// exclude directive only works in FF
if (document.location.href.match('www.flickr.com/photos/organize')) {
    return;
}

var debug = false; //document.location.href.match('api');

var images = {
    plus: chrome.extension.getURL('images/plus.png'),
    minus: chrome.extension.getURL('images/minus.png'),
    mailicon: chrome.extension.getURL('images/mailicon.png'),
    color: chrome.extension.getURL('images/color.png'),
    remove: chrome.extension.getURL('images/remove.png'),
    lock: chrome.extension.getURL('images/lock.png'),
    down: chrome.extension.getURL('images/down.png'),
    up: chrome.extension.getURL('images/up.png'),
    error: chrome.extension.getURL('images/error.png'),
    ok: chrome.extension.getURL('images/ok.png'),
    updating: chrome.extension.getURL('images/pulser2.gif'),
    spaceout: chrome.extension.getURL('images/spaceout.gif')
};

new Element('span', {
	id: 'NFPN.counter',
	html: 'counter',
	events: {
		'update-count': function(evt) {
			this.set('html', evt);
		}
	}
}).inject($('head-upload-link'), 'after');

var backgroundPort = chrome.extension.connect({name: "flickrnotifications"});
backgroundPort.onMessage.addListener(function (msg) {
    if (msg.stat == 'error') {
        GM_log("ERROR: " + msg.statusText);
    }
    $(msg.callbackId).fireEvent(msg.callbackEvent, msg);
});
//backgroundPort.postMessage({name: 'testEvents', callbackEvent: 'set-count', callbackId: 'NFPN.counter'});
// we can do with only one!
chrome.extension.onRequest.addListener(function (msg) {
GM_log("received event");
	$(msg.id).fireEvent(msg.event, msg.data);
});

function getVersion(callbackId, callbackEvent) {
	backgroundPort.postMessage({name: 'getVersion', callbackEvent: callbackEvent, callbackId: callbackId});
}

var preferences = undefined;

// hack to circumvent 'bug' when overriding toString (and others):
// https://mootools.lighthouseapp.com/projects/2706/tickets/651-classtostring-broken-on-122-big-regression
['toString', 'toLocaleString', 'valueOf', 'toSource', 'watch', 'unwatch', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable'].each(function (method) {
	Class.Mutators[method] = $arguments(0);
});

// only run when the user is logged in
var loggedIn = $chk($('MessageIcon')) || $chk($('mail-icon'));
if (!loggedIn) {
    throw new Error("not logged in; aborting");
}

var x11Colors = [ // http://www.w3.org/TR/2002/WD-css3-color-20020219/#x11-color
'AliceBlue',
'AntiqueWhite',
'Aqua',
'Aquamarine',
'Azure',
'Beige',
'Bisque',
'Black',
'BlanchedAlmond',
'Blue',
'BlueViolet',
'Brown',
'BurlyWood',
'CadetBlue',
'Chartreuse',
'Chocolate',
'Coral',
'CornflowerBlue',
'Cornsilk',
'Crimson',
'Cyan',
'DarkBlue',
'DarkCyan',
'DarkGoldenrod',
'DarkGray',
'DarkGreen',
'DarkKhaki',
'DarkMagenta',
'DarkOliveGreen',
'DarkOrange',
'DarkOrchid',
'DarkRed',
'DarkSalmon',
'DarkSeaGreen',
'DarkSlateBlue',
'DarkSlateGray',
'DarkTurquoise',
'DarkViolet',
'DeepPink',
'DeepSkyBlue',
'DimGray',
'DodgerBlue',
'FireBrick',
'FloralWhite',
'ForestGreen',
'Fuchsia',
'Gainsboro',
'GhostWhite',
'Gold',
'Goldenrod',
'Gray',
'Green',
'GreenYellow',
'Honeydew',
'HotPink',
'IndianRed',
'Indigo',
'Ivory',
'Khaki',
'Lavender',
'LavenderBlush',
'LawnGreen',
'LemonChiffon',
'LightBlue',
'LightCoral',
'LightCyan',
'LightGoldenrodYellow',
'LightGreen',
'LightGrey',
'LightPink',
'LightSalmon',
'LightSeaGreen',
'LightSkyBlue',
'LightSlateGray',
'LightSteelBlue',
'LightYellow',
'Lime',
'LimeGreen',
'Linen',
'Magenta',
'Maroon',
'MediumAquamarine',
'MediumBlue',
'MediumOrchid',
'MediumPurple',
'MediumSeaGreen',
'MediumSlateBlue',
'MediumSpringGreen',
'MediumTurquoise',
'MediumVioletRed',
'MidnightBlue',
'MintCream',
'MistyRose',
'Moccasin',
'NavajoWhite',
'Navy',
'OldLace',
'Olive',
'OliveDrab',
'Orange',
'OrangeRed',
'Orchid',
'PaleGoldenrod',
'PaleGreen',
'PaleTurquoise',
'PaleVioletRed',
'PapayaWhip',
'PeachPuff',
'Peru',
'Pink',
'Plum',
'PowderBlue',
'Purple',
'Red',
'RosyBrown',
'RoyalBlue',
'SaddleBrown',
'Salmon',
'SandyBrown',
'SeaGreen',
'Seashell',
'Sienna',
'Silver',
'SkyBlue',
'SlateBlue',
'SlateGray',
'Snow',
'SpringGreen',
'SteelBlue',
'Tan',
'Teal',
'Thistle',
'Tomato',
'Turquoise',
'Violet',
'Wheat',
'White',
'WhiteSmoke',
'Yellow',
'YellowGreen' ];


        // the following api_key is reserved for this application
        // if you need an api_key for your own application, please request one at 
        // http://www.flickr.com/services/apps/create/apply/
        // if you request a Non-Commercial key, you'll get it instantly
var api_key = 'f34fa6453715520a1a280fa8cee46ec4'; // the app's own key

if (debug) GM_log("DEBUGGING");

var NFPNPreferences = new Class({
    options: {
        gotoLastThreadPage: false,
        colorizeNewPosts: true,
        scrollToNewPosts: true,
        defaultCheckDelay: 0,
        sortIcons: false, // TODO: defaults to true
        compactEnvelopeStyle: false,
        checkDelayFor: {}
    },
    initialize: function () {
        var storedGotoLastThreadPage = GM_getValue("NFPN.preference.gotoLastThreadPage");
        if (storedGotoLastThreadPage !== undefined) {
            this.options.gotoLastThreadPage = (storedGotoLastThreadPage === true || storedGotoLastThreadPage === "true"); // Google chrome trickery
        }
        var storedColorizeNewPosts = GM_getValue("NFPN.preference.colorizeNewPosts");
        if (storedColorizeNewPosts !== undefined) {
            this.options.colorizeNewPosts = (storedColorizeNewPosts === true || storedColorizeNewPosts === "true");
        }
        var storedScrollToNewPosts = GM_getValue("NFPN.preference.scrollToNewPosts");
        if (storedScrollToNewPosts !== undefined) {
            this.options.scrollToNewPosts = (storedScrollToNewPosts === true || storedScrollToNewPosts === "true");
        }
        var storedDefaultCheckDelay = GM_getValue("NFPN.preference.defaultCheckDelay");
        if (storedDefaultCheckDelay !== undefined) {
            try {
                this.options.defaultCheckDelay = parseInt(storedDefaultCheckDelay, 10);
            } catch (e) {
                GM_log("error parsing '" + storedDefaultCheckDelay + "' to an integer; setting defaultCheckDelay to 0");
                this.options.defaultCheckDelay = 0;
            }
        }
        var storedCheckDelays = GM_getObject("NFPN.preference.checkDelays");
        if (storedCheckDelays.key_present) {
            if (storedCheckDelays.value) {
                this.options.checkDelayFor = storedCheckDelays.value;
            } else {
                // TODO: post a big warning
                // can not do: no link, no supported 'type' :)
            }
        }
        var sortIcons = GM_getValue("NFPN.preference.sortIcons");
        if (sortIcons !== undefined) {
            this.options.sortIcons = (sortIcons === true || sortIcons === "true");
        }
        var compactEnvelopeStyle = GM_getValue("NFPN.preference.compactEnvelopeStyle");
        if (compactEnvelopeStyle !== undefined) {
            this.options.compactEnvelopeStyle = (compactEnvelopeStyle === true || compactEnvelopeStyle === "true");
        }
    },
    gotoLastThreadPage: function () {
        return this.options.gotoLastThreadPage;
    },
    setGotoLastThreadPage: function (b) {
        if (b !== this.options.gotoLastThreadPage) {
            GM_setValue("NFPN.preference.gotoLastThreadPage", b);
            this.options.gotoLastThreadPage = b;
            return true;
        }
        return false;
    },
    colorizeNewPosts: function () {
        return this.options.colorizeNewPosts;
    },
    setColorizeNewPosts: function (b) {
        if (b !== this.options.colorizeNewPosts) {
            GM_setValue("NFPN.preference.colorizeNewPosts", b);
            this.options.colorizeNewPosts = b;
            return true;
        }
        return false;
    },
    scrollToNewPosts: function () {
        return this.options.scrollToNewPosts;
    },
    setScrollToNewPosts: function (b) {
        if (b !== this.options.scrollToNewPosts) {
            GM_setValue("NFPN.preference.scrollToNewPosts", b);
            this.options.scrollToNewPosts = b;
            return true;
        }
        return false;
    },
    defaultCheckDelay: function () {
        return this.options.defaultCheckDelay;
    },
    setDefaultCheckDelay: function (d) {
        try {
            var newValue = parseInt(d, 10);
        } catch (e) {
            return false;
        }
        if (newValue !== this.options.defaultCheckDelay) {
            GM_setValue("NFPN.preference.defaultCheckDelay", newValue);
            this.options.defaultCheckDelay = newValue;
            return true;
        }
        return false;
    },
    sortIcons: function () {
        return this.options.sortIcons;
    },
    setSortIcons: function (s) {
        if (s !== this.options.sortIcons) {
            GM_setValue("NFPN.preference.sortIcons", s);
            return true;
        }
    },
    compactEnvelopeStyle: function () {
        return this.options.compactEnvelopeStyle;
    },
    setEnvelopeFlickrStyle: function (e) {
        if (e !== this.options.compactEnvelopeStyle) {
            GM_setValue("NFPN.preference.compactEnvelopeStyle", e);
        }
    },
    checkDelayFor: function (id) {
        var retval = this.options.checkDelayFor[id];
        if ($chk(retval)) {
            return parseInt(retval, 10);
        } else {
            this.options.checkDelayFor[id] = -1;
            return -1;
        }
    },
    setCheckDelayFor: function (id, d) {
        try {
            var newValue = parseInt(d, 10);
        } catch (e) {
            return false;
        }
        if (newValue !== this.checkDelayFor(id)) {
            this.options.checkDelayFor[id] = d;
            GM_storeObject("NFPN.preference.checkDelays", this.options.checkDelayFor);
            return true;
        }
        return false;
    }
});

var preferencesInitData = {
    photo: {
        type: 'photo', // used in DOM id's => no funny characters!
        gm_keyPostfix: 'Photo', // to create /^selectedPhoto./
        headerTitle: 'Selected photos',
        identifier: 'photoId',
	newitems: 'newComments',
	items: 'comments',
	error: 'error',
        titleTemplate: {
            text: '%1 (by <i>%2</i>)',
            param1: 'title',
            param2: 'username'
        },
        text: 'photo', // to be inserted into 'remove this photo from ...'
        checkForNewCallback: checkForNewCommentsInPhotoPage
    },
    photostream: {
        type: 'photostream',
        gm_keyPostfix: 'Photostream',
        headerTitle: 'Selected photostreams',
        identifier: 'userNsid',
	newitems: 'newPhotos',
	items: 'photos',
	error: 'error',
        text: 'photostream',
        checkForNewCallback: checkForNewPhotosInPhotostream
    },
    thread: {
        type: 'thread',
        gm_keyPostfix: 'Thread',
        headerTitle: 'Selected threads',
        identifier: 'topic',
	newitems: 'newPosts',
	items: 'replies',
	error: 'error',
        titleTemplate: {
            text: '%1 (from group <i>%2</i>)',
            param1: 'title',
            param2: 'groupname'
        },
        text: 'thread',
        checkForNewCallback: checkForNewPostsInThread
    },
    group: {
        type: 'group',
        gm_keyPostfix: 'Group',
        headerTitle: 'Selected group discussion pages',
        identifier: 'groupname',
	newitems: 'newPosts',
	items: 'replies',
	error: 'invalidCount',
        text: 'discussion group',
        cleanupOnDeleteCallback: removeThreadsFromCache,
        checkForNewCallback: checkForNewPostsInGroup
    },
    helpforum: {
        type: 'helpforum',
        gm_keyPostfix: 'Helpforum',
        headerTitle: 'Selected help forums',
        identifier: 'groupname',
	newitems: 'newPosts',
	items: 'replies',
	error: 'invalidCount',
        text: 'help forum',
        titleTemplate: {
            text: '%1 (in <i>%2</i>)',
            param1: 'title',
            param2: 'groupname'
        },
        cleanupOnDeleteCallback: removeThreadsFromCache,
        checkForNewCallback: checkForNewPostsInHelpForum
    },
    forumitem: {
        type: 'forumitem',
        gm_keyPostfix: 'Forumitem',
        headerTitle: 'Selected forum threads',
        identifier: 'topic',
	newitems: 'newPosts',
	items: 'replies',
	error: 'error',
        text: 'forum thread',
        checkForNewCallback: checkForNewPostsInForumThread
    },
    groupmembers: {
        type: 'groupmembers',
        gm_keyPostfix: 'Groupmembers',
        headerTitle: 'Selected group member pages',
        identifier: 'groupId',
	newitems: 'newMembers',
	items: 'members',
	error: 'error',
        text: 'group members',
        checkForNewCallback: checkForNewMembersInGroup
    },
    grouppool: {
        type: 'grouppool',
        gm_keyPostfix: 'Grouppool',
        headerTitle: 'Selected group pools',
        identifier: 'groupId',
	newitems: 'newPhotos',
	items: 'photos',
	error: 'error',
        text: 'group pool',
        checkForNewCallback: checkForNewPhotosInGroupPool
    },
    reversedcontacts: {
        type: 'reversedcontacts',
        gm_keyPostfix: 'Reversedcontacts',
        headerTitle: 'Selected reversed contacts pages',
        identifier: 'userNsid',
	newitems: 'newContacts',
	items: 'members',
	error: 'error',
        text: 'reversed contact',
        checkForNewCallback: checkForNewReversedContacts
    },
    pendingitems: {
        type: 'pendingitems',
        gm_keyPostfix: 'Pendingitems',
        headerTitle: 'Selected pending items pages',
        identifier: 'groupname',
	newitems: 'newItems',
	items: 'items',
	error: 'error',
        text: 'pending items',
        checkForNewCallback: checkForNewPendingItems
    },
    application: {
        type: 'application',
        gm_keyPostfix: 'App',
        headerTitle: 'Selected applications',
        identifier: 'appId',
	newitems: 'newComments',
	items: 'comments',
	error: 'error',
        titleTemplate: {
            text: '%1 (by <i>%2</i>)',
            param1: 'title',
            param2: 'username'
        },
        text: 'app',
        checkForNewCallback: checkForNewAppComments
    },
    set: {
        type: 'set',
        gm_keyPostfix: 'Set',
        headerTitle: 'Selected sets',
        identifier: 'setId',
	newitems: 'newComments',
	items: 'comments',
	error: 'error',
        titleTemplate: {
            text: '%1 (by <i>%2</i>)',
            param1: 'title',
            param2: 'realname'
        },
        text: 'set',
        checkForNewCallback: checkForNewSetComments
    },
    pendingtestimonials: {
    	type: 'pendingtestimonials',
	gm_keyPostfix: 'Pendingtestimonials',
	headerTitle: 'Pending testimonials',
	identifier: 'userNsid',
	newitems: 'newItems',
	items: 'items',
	error: 'error',
	text: 'pending testimonials',
	checkForNewCallback: checkForNewPendingTestimonials
    }
};

function applyPreferences() {
    // last thread page
    var gotoLastThreadPageCheckbox = $('NFPN.gotolastthreadpage');
    if ($chk(gotoLastThreadPageCheckbox)) {
        var gotoLastPage = $('NFPN.gotolastthreadpage').checked;
        var changed = preferences.setGotoLastThreadPage(gotoLastPage);
        if (changed) { // change links on the fly
            var anchors = [];
            // add/remove 'lastpage' from thread and forumitem urls
            anchors.combine($$('img[id^=NFPN.'  + preferencesInitData.thread.gm_keyPostfix + ']').getParent('a'))
                   .combine($$('span[id^=NFPN.' + preferencesInitData.thread.gm_keyPostfix + ']').getElement('a'));
            anchors.combine($$('img[id^=NFPN.'  + preferencesInitData.forumitem.gm_keyPostfix + ']').getParent('a'))
                   .combine($$('span[id^=NFPN.' + preferencesInitData.forumitem.gm_keyPostfix + ']').getElement('a'));
            anchors.each(function (anchor) {
                if ($chk(anchor)) {
                    if (gotoLastPage) { // add '/lastpage'
                        anchor.set('href', anchor.get('href') + '/lastpage');
                    } else { // remove '/lastpage'
                        anchor.set('href', anchor.get('href').replace(/\/lastpage$/, ''));
                    }
                } 
            });
            // add/remove 'pageX' from photo urls
            anchors = [];
            anchors.combine($$('img[id^=NFPN.'  + preferencesInitData.photo.gm_keyPostfix + '\.]').getParent('a'))
                   .combine($$('span[id^=NFPN.' + preferencesInitData.photo.gm_keyPostfix + '\.]').getElement('a'));
            anchors.each(function (anchor) {
                if ($chk(anchor)) {
                    try {
                        var photoid = anchor.get('href').match(/.*flickr.com\/photos\/[^\/]+\/(\d+)/)[1];
                        if (gotoLastPage) { // add '/pageX'
                            var metaInfo = GM_getObject('selected' + preferencesInitData.photo.gm_keyPostfix + '.' + photoid);
                            if (metaInfo.key_present) {
                                var photoInfo = metaInfo.value;
                                var page = Math.ceil(photoInfo.comments / 50 );
                                if (page > 1) {
                                    anchor.set('href', anchor.get('href') + 'page' + page + '/');
                                }
                            }
                        } else { // remove '/pageX'
                            anchor.set('href', anchor.get('href').replace(/page\d+\/$/, ''));
                        }
                    } catch (e) {
                        GM_log("error: " + e);
                    }
                }
            });
        }
    }

    var colorizeNewPostsCheckbox = $('NFPN.colorizenewposts');
    if ($chk(colorizeNewPostsCheckbox)) {
        var colorizeNewPosts = $('NFPN.colorizenewposts').checked;
        preferences.setColorizeNewPosts(colorizeNewPosts);
    }

    var scrollToNewPostsCheckbox = $('NFPN.scrolltonewposts');
    if ($chk(scrollToNewPostsCheckbox)) {
        var scrollToNewPosts = $('NFPN.scrolltonewposts').checked;
        preferences.setColorizeNewPosts(scrollToNewPosts);
    }

    var sortIconsCheckBox = $('NFPN.sorticons');
    if ($chk(sortIconsCheckBox)) {
        var sortIcons = sortIconsCheckBox.checked;
        preferences.setSortIcons(sortIcons);
    }
    var compactEnvelopeStyleCheckBox = $('NFPN.compactEnvelopeStyle');
    if ($chk(compactEnvelopeStyleCheckBox)) {
        var compactEnvelopeStyle = compactEnvelopeStyleCheckBox.checked;
        preferences.setEnvelopeFlickrStyle(compactEnvelopeStyle);
    }
    // default check delay
    var defaultCheckDelay = $('NFPN.defaultCheckDelay').getSelected()[0].value;
    //GM_log("default set to : " + defaultCheckDelay);
    preferences.setDefaultCheckDelay(defaultCheckDelay);

    $each(preferencesInitData, function (initData) {
        // check delay
        try {
            var checkDelay = $('NFPN.checkDelay.' + initData.gm_keyPostfix).getSelected()[0].value;
            preferences.setCheckDelayFor(initData.gm_keyPostfix, checkDelay);
        } catch (e) {
            // if some 'type' is not selected, it's not on the screen
        }

        // removals
        $$('img.NFPN-' + initData.type + '-remove').each( function (imgElement) {
            var id = imgElement.get('id').replace('NFPN.' + initData.type + '.remove.', '');
            // remove from data store
            GM_deleteValue('selected' + initData.gm_keyPostfix + '.' + id);
            if ($chk(initData.cleanupOnDeleteCallback)) {
                initData.cleanupOnDeleteCallback(id);
            }
            // remove from screen
            try {
                $('NFPN.' + initData.gm_keyPostfix + '.' + id).getParent('a').dispose();
                $('NFPN.' + initData.gm_keyPostfix + '.' + id + '.MessageChunk').dispose();
            } catch (e) {
                try {// if the item was only just added, it has no anchor parent (and no messagechunk)
                    $('NFPN.' + initData.gm_keyPostfix + '.' + id).dispose();
                } catch (e) {
                    GM_log(e);
                }
            }
        });
        // envelope colors
        $$('img.NFPN-' + initData.type + '-bgcolor').each( function (imgElement) {
            var id = imgElement.get('id').replace('NFPN.' + initData.type + '.bgColor.', '');
            var metaInfo = GM_getObject('selected' + initData.gm_keyPostfix + '.' + id);
            if (metaInfo.key_present) { // could have been removed
                var storedInfo = metaInfo.value;
                if (storedInfo) {
                    var bgColor = imgElement.getAttribute("nfpncolor");
                    if (bgColor !== storedInfo.bgColor) {
                        storedInfo.bgColor = bgColor;
                        GM_storeObject('selected' + initData.gm_keyPostfix + '.' + id, storedInfo);
                        $('NFPN.' + initData.gm_keyPostfix + '.' + id).style.backgroundColor = bgColor;
                    }
                } else {
                    // nothing to do: should have been reported when opening the preferences dialog
                }
            }
        });
        // check delays
        $$('select.NFPN-' + initData.type + '-checkdelay').each( function (selectElement) {
            var id = selectElement.get('id').replace('NFPN.checkDelay.' + initData.gm_keyPostfix + '.', '');
            var metaInfo = GM_getObject('selected' + initData.gm_keyPostfix + '.' + id);
            if (metaInfo.key_present) { // could have been removed
                var storedInfo = metaInfo.value;
                if (storedInfo) {
                    if (selectElement.selectedIndex === 0) { // default
                        if (!$chk(storedInfo.checkDelay)) { // no change
                            return;
                        }
                        storedInfo.checkDelay = undefined;
                        GM_storeObject('selected' + initData.gm_keyPostfix + '.' + id, storedInfo);
                    } else {
                        var checkDelay = selectElement.getSelected()[0].value;
                        if (storedInfo.checkDelay == checkDelay) { // no change
                            return;
                        }
                        storedInfo.checkDelay = checkDelay;
                        GM_storeObject('selected' + initData.gm_keyPostfix + '.' + id, storedInfo);
                    }
                }
            }
        });
    });
    
/*    if (somethingChanged) {
        window.location.reload();
    } else { */
        togglePreferencesDialog();
/*    } */
}

function createColorCell(html, title, bgColor, selectedColor, colorName) {
    if (colorName === undefined) {
        colorName = bgColor;
    }
    var colorCell = new Element('td', {
        html: html,
        title: title,
        width: 17,
        height: 20,
        align: 'center',
        styles: {
            background: bgColor,
            cursor: 'pointer'
        },
        events: {
            click: function (evt) {
                // remove old checkmark
                this.getParent('table').getElements('img[src='+images.ok+']').dispose();
                // set new color
                this.getParent('div').getElement('img.nfpn-colorpicker').setAttribute('nfpncolor', colorName);
                this.getParent('div').getElement('img.nfpn-colorpicker').style.backgroundColor = 
                    colorName === 'none' ? '' : bgColor;
                // put new checkmark
                new Element('img', { // known issue: click twice on a cell, and there's no checkmark?
                    src: images.ok
                }).inject($(evt.target));
            }
        }
    })
    if (selectedColor === colorName) {
        new Element('img', {
            src: images.ok
        }).inject(colorCell);
    }
    return colorCell;
}

function createColorsTable(bgColor) {
    var colorsTable = new Element('table', {
        cellPadding: 0,
        cellSpacing: 1,
        styles: {
            display: 'inline',
        }
    });
    var colorsRow;
    x11Colors.each( function (color, idx) {
        if (idx === 35) {
/*            colorCell = createColorCell('Flickr', 
                'same as Flickr background',
                $(document).getElement('body').getComputedStyle('background-color'),
                bgColor,
                'Flickr');
            colorCell.rowSpan = 4;
            colorCell.inject(colorsRow); */
            colorCell = createColorCell('none',
                'don\'t use a background color',
                'white',
                bgColor,
                'none');
            colorCell.rowSpan = 4;
            colorCell.inject(colorsRow);
            if (bgColor === undefined || bgColor === null) {
                colorCell.adopt(new Element('img', {
                    src: images.ok
                }));
            }
        }
        if ( (idx % 35) === 0 ) {
            colorsRow = new Element('tr').inject(colorsTable);
        } 
                                                    
        createColorCell(' ',
            color,
            color,
            bgColor
        ).inject(colorsRow);
    });
    return colorsTable;
}

function createRemoveIcon(id, title, removeClass) {
    return new Element('img', {
        src: images.remove,
        id: id,
        title: title,
        styles: {
            cursor: 'pointer'
        },
        events: {
            click: function (evt) {
                this.toggleClass(removeClass);
		if (this.hasClass(removeClass)) {
                	this.getParent('div').setStyle('text-decoration', 'line-through');
		} else {
			this.getParent('div').setStyle('text-decoration', '');
		}
            }
        }
    });
}

function createBgColorIcon(id, title, selectedColor, colorClass) {
    return new Element('img', {
        src: images.color,
        id: id,
        title: title,
        styles: {
            cursor: 'pointer',
            backgroundColor: (selectedColor === undefined || selectedColor === 'none' ? '' :
                         selectedColor === 'Flickr' ? $(document).getElement('body').getComputedStyle('background-color') :
                         selectedColor)
        },
        'class': 'nfpn-colorpicker ' + colorClass,
        nfpncolor: selectedColor,
        events: {
            click: function (evt) {
                if (this.hasClass('showing')) {
                    this.removeClass('showing');
                    this.getParent().getElement('span.colortable').destroy();
                } else {
                    this.addClass('showing');
                    var bgColor = this.getAttribute('nfpncolor');
                    var colorsTable = createColorsTable(bgColor);
                    colorsTable.inject(new Element('span', {
                        'class': 'colortable'
                    }).inject(this.getParent('div')));
                }
            }
        }
    });
}

function togglePreferencesDialog(evt) {
    // close the menu
    $$('#candy_nav_menu_organize', '#flickr_nav_menu_organize').setStyle('display', 'none'); 
    $$('#candy_nav_button_organize', '#flickr_nav_button_organize').removeClass('hover').removeClass('selected').removeClass('nav_clicked_open')
                .getElement('img').set('src', 'http://l.yimg.com/g/images/site_nav_caret_split_default.png');
    var preferencesDialog = $('NFPN.preferences');
    if ($chk(preferencesDialog)) {
        preferencesDialog.destroy();
    } else {
        var delayOptions = [
            { unit: 'second',   value: 0,   multiplier: 1 },
            { unit: 'minute',   value: 1,   multiplier: 60 },
            { unit: 'minute',   value: 5,   multiplier: 60 },
            { unit: 'minute',   value: 30,  multiplier: 60 },
            { unit: 'hour',     value: 1,   multiplier: (60 * 60) },
            { unit: 'hour',     value: 4,   multiplier: (60 * 60) },
            { unit: 'day',      value: 1,   multiplier: (24 * 60 * 60) },
	    { unit: 'week',	value: 1,   multiplier: (7 * 24 * 60 * 60) }
        ];

        var topline = evt.event.pageY < 50 ? evt.event.pageY : 50;
        var preferencesTable = new Element('div', {
            id: 'NFPN.preferences',
            styles: {
                overflow: 'auto',
                background: '#BFBFBF',
                '-moz-border-radius': '4px',
                '-webkit-border-radius': '4px',
                '-khtml-border-radius': '4px',
                'border-radius': '4px',
                border: 'grey solid 1px',
                padding: '2px 4px',
                zIndex: 1014,
                maxHeight: (window.innerHeight - topline - 20),
                maxWidth: (window.innerWidth - 50),
                minWidth: 640,
                position: 'fixed',
                opacity: '0.98',
                left: '10px', //evt.event.pageX,
                top: topline,
                display: 'block',
                visibility: 'visible'
            }
        }).inject($(document).getElement('body'));
        var gotoLastThreadPageCheckbox;
        var colorizeNewPostsCheckbox;
        var scrollToNewPostsCheckbox;
        var sortIconsCheckbox;
        var compactEnvelopeStyleCheckBox;
        new Element('div', {
	    id: 'NFPN.preferences_title',
            html: 'Flickr Notifications - Preferences',
            styles: {
                fontWeight: 'bold',
                width: preferencesTable.getStyle('maxWidth').match(/(\d+)/)[1] - 50
            },
	    events: {
	    	'version': function (evt) {
			this.set('html', 'Flickr Notifications v' + evt.version + ' - Preferences');
		}
	    }
        }).inject(preferencesTable);
	getVersion('NFPN.preferences_title', 'version');

        // general preferences - global defaults
        var delayCombo;
        var generalDelay = new Element('div', {
            align: 'left'
        }).inject(preferencesTable);
        generalDelay.adopt(
            new Element('div', {
                styles: {
                    minWidth: 300,
                    float: 'left',
                    display: 'inline'
                }
            }).adopt(
                new Element('span', {
                    html: 'delay between checks: '
                }),
                delayCombo = new Element('select', {
                    id: 'NFPN.defaultCheckDelay',
                    events: {
                        change: function (evt) {
                                $each(preferencesInitData, function (initData) {
                                    var keyValues = GM_listValues();
                                    var delaySelectForType = $('NFPN.checkDelay.' + initData.gm_keyPostfix);
                                    if (!$chk(delaySelectForType)) {
                                        return;
                                    }
                                    var defaultDelayOptionForType = delaySelectForType.getElement('option');
                                    defaultDelayOptionForType.set('html', 'default (' + 
                                                $('NFPN.defaultCheckDelay').getSelected()[0].get('html') + ')');
                                    // propagating a 'change' event on the second level defaults does not work
                                    // doing it manually
                                    // only when the type's delay is set to 'default'
                                    if (delaySelectForType.selectedIndex === 0) {
                                        var reKey = new RegExp('^selected' + initData.gm_keyPostfix + '\\.');
                                        $each(keyValues, function (key) {
                                            if (key.match(reKey)) {
                                                var id = key.replace('selected' + initData.gm_keyPostfix + '.', '');
                                                var delaySelectForItem = 
                                                            $('NFPN.checkDelay.' + initData.gm_keyPostfix + '.' + id);
                                                if (!delaySelectForItem) {
                                                    GM_log("missing select: '" + 'NFPN.checkDelay.' + initData.gm_keyPostfix + '.' + id);
                                                    return;
                                                }
                                                var defaultOption = delaySelectForItem.getElement('option');
                                                defaultOption.set('html', 'default (' + 
                                                    $(evt.target).getSelected()[0].get('html') + ')');
                                            }
                                        });
                                    }
                                });
                            }
                    }
                })
            ),
            new Element('div', {
                styles: {
                    fontStyle: 'italic',
                    float: 'left',
                    maxWidth: preferencesTable.getStyle('maxWidth').match(/(\d+)/)[1] - 400
                },
                html: "Wait at least the given amount of time before checking numbers again. This value will be used as " +
                      "the default for each supported type, unless specified otherwise.<br>" +
                      "(0 seconds: check on every page visit)"
            })
        );
        // go to last page
        var gotoLastPagePreference = new Element('div', {
            align: 'left',
            clear: 'both'
        }).inject(preferencesTable);
        gotoLastPagePreference.adopt(
            new Element('div', {
                styles: {
                    minWidth: 300,
                    float: 'left'
                }
            }).adopt(
                gotoLastThreadPageCheckbox = new Element('input', {
                    type: 'checkbox',
                    id: 'NFPN.gotolastthreadpage'
                }),
                new Element('label', {
                    'for': 'NFPN.gotolastthreadpage',
                    html: 'go to the thread\'s last page'
                })
            ),
            new Element('div', {
                styles: {
                    fontStyle: 'italic',
                    float: 'left',
                    maxWidth: preferencesTable.getStyle('maxWidth').match(/(\d+)/)[1] - 350
                },
                html: "The link on the envelopes take you to the first page of the thread. " +
                      "<br>With this option set, the link will take you to the the last page of the thread." + 
                      "<br>Applicable for discussion threads, forum items, and photo pages (not group " +
                      "<br>discussion pages, or group pools, ..)"
            })
        );
        if (preferences.gotoLastThreadPage()) {
            gotoLastThreadPageCheckbox.set('checked', 'checked');
        }
        // compact notation
        var compactPreference = new Element('div', { 
            align: 'left',
            clear: 'both'
        }).inject(preferencesTable);
        compactPreference.adopt(
            new Element('div', {
                styles: {
                    minWidth: 300,
                    float: 'left'
                }
            }).adopt(
                compactEnvelopeStyleCheckBox = new Element('input', {
                    type: 'checkbox',
                    id: 'NFPN.compactEnvelopeStyle'
                }),
                new Element('label', {
                    html: 'compact envelope style',
                    'for': 'NFPN.compactEnvelopeStyle'
                })
            ),
            new Element('div', {
                styles: {
                    fontStyle: 'italic',
                    float: 'left',
                    maxWidth: preferencesTable.getStyle('maxWidth').match(/(\d+)/)[1] - 350
                },
                html: "Uses a more compact notation for the notification mail icons.<br/>" +
                      "(needs a page reload to take effect)"
            })
        );
        if (preferences.compactEnvelopeStyle()) {
            compactEnvelopeStyleCheckBox.set('checked', 'checked');
        }

        var colorizeNewPostsPreference = new Element('div', {
            clear: 'both',
            align: 'left'
        }).inject(preferencesTable);
        colorizeNewPostsPreference.adopt(
            new Element('div', {
                styles: {
                    minWidth: 300,
                    display: 'inline',
                    float: 'left'
                }
            }).adopt(
                colorizeNewPostsCheckbox = new Element('input', {
                    type: 'checkbox',
                    id: 'NFPN.colorizenewposts'
                }),
                new Element('label', {
                    'for': 'NFPN.colorizenewposts',
                    html: 'colorize new posts'
                })
            ),
            new Element('div', {
                styles: {
                    fontStyle: 'italic',
                    float: 'left',
                    maxWidth: preferencesTable.getStyle('maxWidth').match(/(\d+)/)[1] - 350
                },
                html: "Colorizes new posts to easily find them." + 
                      "<br>Applicable for discussion threads, help forum items and photo pages."
            })
        );
        if (preferences.colorizeNewPosts()) {
            colorizeNewPostsCheckbox.set('checked', 'checked');
        }

        var scrollToNewPostsPreference = new Element('div', {
            clear: 'both',
            align: 'left'
        }).inject(preferencesTable);
        scrollToNewPostsPreference.adopt(
            new Element('div', {
                styles: {
                    minWidth: 300,
                    display: 'inline',
                    float: 'left'
                }
            }).adopt(
                scrollToNewPostsCheckbox = new Element('input', {
                    type: 'checkbox',
                    id: 'NFPN.scrolltonewposts'
                }),
                new Element('label', {
                    'for': 'NFPN.scrolltonewposts',
                    html: 'scroll down to new posts'
                })
            ),
            new Element('div', {
                styles: {
                    fontStyle: 'italic',
                    float: 'left',
                    maxWidth: preferencesTable.getStyle('maxWidth').match(/(\d+)/)[1] - 350
                },
                html: "Scrolls a page down to the new posts." + 
                      "<br>Applicable for discussion threads and help forum items (and very dependent on script order)."
            })
        );
        if (preferences.scrollToNewPosts()) {
            scrollToNewPostsCheckbox.set('checked', 'checked');
        }

/*        new Element('tr', {
 *          'class': 'NFPN-preference',
            styles: {
                display: 'none',
                visibility: 'hidden'
            }
 *        }).adopt(
            new Element('td', {
                noWrap: 'nowrap',
                vAlign: 'top'
            }).adopt(
                sortIconsCheckBox = new Element('input', {
                    type: 'checkbox',
                    id: 'NFPN.sorticons'
                }),
                new Element('label', {
                    html: 'sort notification icons',
                    'for': 'NFPN.sorticons'
                })
            ),
            new Element('td', {
                styles: {
                    fontStyle: 'italic'
                },
                html: "sort the icons within each type (puts threads of the same group together)<br>" +
                      "(needs a page reload to take effect)"
            })
        ).inject(preferencesTable);
        if (preferences.sortIcons()) {
            sortIconsCheckBox.set('checked', 'checked');
        } */ // TODO: implement sorting

        delayOptions.each(function (delayOption) {
            delayCombo.adopt(
                new Element('option', {
                    html: delayOption.value + ' ' + delayOption.unit + (delayOption.value !== 1 ? 's' : ''),
                    value: (delayOption.value * delayOption.multiplier),
                    selected: (preferences.defaultCheckDelay() === (delayOption.value * delayOption.multiplier))
                })
            );
        });
        
        var keyValues = GM_listValues();
        //keyValues.sort();
        $each(preferencesInitData, function (initData) {
            var headerPrinted = false;
            var reKey = new RegExp('^selected' + initData.gm_keyPostfix + '\\.');
            $each(keyValues, function (key) {
                if (key.match(reKey)) {
                    var metaInfo = GM_getObject(key);
                    if (metaInfo.key_present) { // it always is
                        var storedInfo = metaInfo.value;
                        if ($chk(storedInfo)) {
                            if (!headerPrinted) {
                                headerPrinted = true;
                                var header = new Element('div', {
                                    html: initData.headerTitle + ' ',
                                    styles: {
                                        background: '#CFCFCF',
                                        textAlign: 'left',
                                        clear: 'both'
                                    }
                                }).inject(preferencesTable);
                                header.adopt(
                                    new Element('img', {
                                        src: images.down,
                                        title: 'click to expand',
                                        styles: {
                                            height: 12,
                                            cursor: 'pointer'
                                        },
                                        events: {
                                            click: function(evt) {
                                                if (evt.target.title.match(/expand/)) {
                                                    evt.target.src = images.up;
                                                    evt.target.title = 'click to collapse';
                                                    $$('div.NFPN-preference-' + initData.gm_keyPostfix).each(function (pref) {
                                                        pref.style.display = 'block';
                                                        pref.style.visibility = 'visible';
                                                    });
                                                } else {
                                                    evt.target.src = images.down;
                                                    evt.target.title = 'click to expand';
                                                    $$('div.NFPN-preference-' + initData.gm_keyPostfix).each(function (pref) {
                                                        pref.style.display = 'none';
                                                        pref.style.visibility = 'hidden';
                                                    });
                                                }
                                            }
                                        }
                                    })
                                );
                                var typePreferences = new Element('div', {
                                    'class': 'NFPN-preference-' + initData.gm_keyPostfix,
                                    styles: {
                                        display: 'none',
                                        visibility: 'hidden',
                                        textAlign: 'left'
                                    }
                                }).inject(preferencesTable);
                                typePreferences.adopt(
                                    new Element('div', {
                                        styles: {
                                            minWidth: 300,
                                            float: 'left'
                                        }
                                    }).adopt(
                                        new Element('span', {
                                            html: 'delay between checks: '
                                        }),
                                        delayCombo = new Element('select', {
                                            id: 'NFPN.checkDelay.' + initData.gm_keyPostfix,
                                            events: {
                                                change: function (evt) {
                                                    var keyValues = GM_listValues();
                                                    var reKey = new RegExp('^selected' + initData.gm_keyPostfix + '\\.');
                                                    $each(keyValues, function (key) {
                                                        if (key.match(reKey)) {
                                                            var id = key.replace('selected' + initData.gm_keyPostfix + '.', '');
                                                            var itemCheckDelay = $('NFPN.checkDelay.' + initData.gm_keyPostfix + '.' + id);
                                                            var defaultOption = itemCheckDelay.getElement('option');
                                                            defaultOption.set('html', 'default (' + 
                                                                    $(evt.target).getSelected()[0].get('html').
                                                                        replace(/^default \((.*)\)/,'$1') + ')');
                                                        }
                                                    });
                                                }
                                            }
                                        })
                                    ),
                                    new Element('div', {
                                        styles: {
                                            fontStyle: 'italic',
                                            maxWidth: 340,
                                            float: 'left'
                                        },
                                        html: "Wait at least the given amount of time before checking " + 
                                                initData.headerTitle.toLowerCase() + " again."
                                    })
                                );
                                delayCombo.adopt(new Element('option', {
                                    html: 'default (' + $('NFPN.defaultCheckDelay').getSelected()[0].get('html') + ')',
                                    value: -1,
                                    selected: (preferences.checkDelayFor(initData.gm_keyPostfix) === -1)
                                }));
                                delayOptions.each(function (delayOption) {
                                    delayCombo.adopt(
                                        new Element('option', {
                                            html: delayOption.value + ' ' + delayOption.unit + 
                                                                                (delayOption.value !== 1 ? 's' : ''),
                                            value: (delayOption.value * delayOption.multiplier),
                                            selected: (preferences.checkDelayFor(initData.gm_keyPostfix) === 
                                                        (delayOption.value * delayOption.multiplier))
                                        })
                                    );
                                });
                            } // headerPrinted
                            var title = storedInfo.title + ' ';
                            if ($chk(initData.titleTemplate)) {
                                title = initData.titleTemplate.text
                                                              .replace('%1', storedInfo[initData.titleTemplate.param1])
                                                              .replace('%2', storedInfo[initData.titleTemplate.param2])
                                        + ' ';
                            }
                            var itemPreferences = new Element('div', {
                                'class': 'NFPN-preference-' + initData.gm_keyPostfix,
                                styles: {
                                    textAlign: 'left',
                                    display: 'none',
                                    visibility: 'hidden',
                                    clear: 'both'
                                }
                            }).inject(preferencesTable);
                            itemPreferences.adopt(
                                    new Element('span', {
                                            html: 'delay: '
                                    }),
                                    delayCombo = new Element('select', {
                                            id: 'NFPN.checkDelay.' + initData.gm_keyPostfix + '.' + storedInfo[initData.identifier],
                                            'class': 'NFPN-' + initData.type + '-checkdelay'
                                    }),
                                    new Element('span', {
                                            html: ' '
                                    }),
                                    createRemoveIcon(
                                            'NFPN.' + initData.type + '.remove.' + storedInfo[initData.identifier],
                                            'remove this ' + initData.text + ' from your watch list',
                                            'NFPN-' + initData.type + '-remove'),
                                    new Element('span', {
                                            html: ' '
                                    }),
                                    createBgColorIcon(
                                            'NFPN.' + initData.type + '.bgColor.' + storedInfo[initData.identifier],
                                            'choose a background color for this ' + initData.text + '\'s envelope',
                                            storedInfo.bgColor,
                                            'NFPN-' + initData.type + '-bgcolor'),
                                    new Element('span', {
                                            html: ' '
                                    }),
                                    new Element('span', {
                                        html: title
                                    })
                            );
                            delayCombo.adopt(new Element('option', {
                                html: 'default (' + $('NFPN.checkDelay.' + initData.gm_keyPostfix).getSelected()[0].get('html').replace(/^default \((.*)\)/, "$1") + ')',
                                value: -1,
                                selected: storedInfo.checkDelay === undefined || storedInfo.checkDelay == -1
                            }));
                            delayOptions.each(function (delayOption) {
                                delayCombo.adopt(
                                    new Element('option', {
                                        html: delayOption.value + ' ' + delayOption.unit + 
                                              (delayOption.value !== 1 ? 's' : ''),
                                        value: (delayOption.value * delayOption.multiplier),
                                        selected: (parseInt(storedInfo.checkDelay, 10) === 
                                                        (delayOption.value * delayOption.multiplier))
                                    })
                                );
                            });
                        } else {
                            // would like to report missing object, but
                            // missing too much information
                        }
                    }
                }
            });
        });

        preferencesTable.adopt(
            new Element('div', {
                align: 'right'
            }).adopt(
                    new Element('button', {
                        type: 'submit',
                        html: 'OK',
                        'class': 'Butt',
                        events: {
                            click: applyPreferences
                        }
                    }),
                    document.createTextNode(' '),
                    new Element('button', {
                        type: 'submit',
                        html: 'Cancel',
                        'class': 'DeleteButt',
                        events: {
                            click: togglePreferencesDialog
                        }
                    })
            )
        );
        /* TODO
        preferencesTable.adopt(
            new Element('div', {
                html: 'Flickr Notifications - Export/Import',
                styles: {
                    fontWeight: 'bold'
                }
            }).adopt(
                    new Element('span', {
                        html: ' '
                    }),
                    new Element('img', {
                        src: images.down,
                        title: 'click to expand',
                        styles: {
                            cursor: 'pointer',
                            height: 14
                        },
                        events: {
                            click: function(evt) {
                                if (evt.target.title.match(/expand/)) {
                                    evt.target.src = images.up;
                                    evt.target.title = 'click to collapse';
                                    $$('tr.NFPN-import-export').each(function (pref) {
                                        pref.style.display = 'block';
                                        pref.style.visibility = 'visible';
                                    });
                                } else {
                                    evt.target.src = images.down;
                                    evt.target.title = 'click to expand';
                                    $$('tr.NFPN-import-export').each(function (pref) {
                                        pref.style.display = 'none';
                                        pref.style.visibility = 'hidden';
                                    });
                                }
                            }
                        }
                    })
            )
        ); */
    }
}

function addPreferencesMenuItem() {
    var preferencesDialogLink = 
        new Element('a', {
            html: 'Flickr Notifications',
            href: 'javascript:void(0)',
            events: {
                click: function (evt) {
                    togglePreferencesDialog(evt);
                }
            }
    });
    var organizeMenu = $('candy_nav_menu_organize');
    if ($chk(organizeMenu)) {
        preferencesDialogLink.inject(organizeMenu);
    } else {
    	organizeMenu = $('flickr_nav_menu_organize');
	if ($chk(organizeMenu)) {
		preferencesDialogLink.inject(new Element('li').inject(organizeMenu));
	}
    }
}

addPreferencesMenuItem();

function retrieveThreadsFromCache(groupname) {
    var retval = new Hash();
    var reMatch = new RegExp("^" + groupname + "\\.");
    $each(GM_listValues(), function(key) {
        if (key.match(reMatch)) {
            var metaInfo = GM_getObject(key);
            if (metaInfo.key_present) { // always true :-)
                var threadInfo = metaInfo.value;
                if (threadInfo) {
                    retval.set(threadInfo.topic, threadInfo);
                } else {
                    GM_deleteValue(key); // clean up
                }
            }
        }
    });
    return retval;
}

function removeThreadsFromCache(groupname) {
    var reMatch = new RegExp("^" + groupname + "\\.");
    $each(GM_listValues(), function(key) {
        if (key.match(reMatch)) {
            GM_deleteValue(key);
        }
    });
}

function resolveUsername(userNsid) {
    var retval = {};
    new Request({
        url: 'http://www.flickr.com',
        async: false,
        onSuccess: function (responseText, responseXML) {
            var result = JSON.parse(responseText);
            if (result.stat === 'fail') {
                GM_log("error reading user information: " + result.message);
                return;
            }
            retval.realname = result.person.realname._content;
            retval.username = result.person.path_alias;
        },
        onFailure: function (response) {
            GM_log("failure reading user information: " + response.statusText);
        }
    }).get('/services/rest', {
        api_key: GM_getMagisterLudi(),
        auth_hash: GM_getAuthHash(),
        auth_token: GM_getAuthToken(),
        format: 'json',
        method: 'flickr.people.getInfo',
        nojsoncallback: 1,
        user_id: userNsid
    });
    return retval;
}

var messageIconId = 'MessageIcon'; // old layout
var unreadMessageChunkId = 'UnreadMessageChunk'; // old layout

function getMessageIcon() {
    var mailIcon = $$('#'+messageIconId)[0];
    if (!$chk(mailIcon)) { // new layout ??
        messageIconId = 'mail-icon';
        unreadMessageChunkId = 'unread-count-container';
        mailIcon = $$('#'+messageIconId)[0];
    }
    return mailIcon;
}
function getUnreadMessageChunk() {
    return $$('#'+unreadMessageChunkId)[0];
}
function getMain() {
	var main = $('Main');
	if (!$chk(main)) main = $('main');
	if (!$chk(main)) main = $('meta');
	return main;
}

function createPostsIcon(prefix, id, url, title, bgColor) {
    if (!$chk($('NFPN.envelopes'))) {
        new Element('div', {
                id: 'NFPN.envelopes',
                styles: {
                        //float: 'right'
                }
        }).inject(getMessageIcon().getParent('a'), 'before');
    }
    try {
        var mailIcon = getMessageIcon();
        var mailAnchor = mailIcon.getParent();
        var messageChunk = getUnreadMessageChunk();

        var newPostsIcon = mailIcon.clone(); // TODO: create span with Flickr sprite
        newPostsIcon.id = 'NFPN.' + prefix + '.' + id;
        var postsAnchor = mailAnchor.clone(false);
        postsAnchor.id = 'NFPN.' + prefix + '.' + id + '.MessageAnchor';
        if (!preferences.compactEnvelopeStyle()) {
            var postsMessageChunk = messageChunk.clone();
            postsMessageChunk.id = 'NFPN.' + prefix + '.' + id + '.MessageChunk';
            var postsMessageChunkAnchor = postsMessageChunk.getElement('a');
            postsMessageChunkAnchor.set('href', url);
            postsMessageChunkAnchor.set('title', title);
            postsMessageChunkAnchor.set('alt', title);
            postsMessageChunk.setStyle('display', 'none');
        }
        newPostsIcon.set('class', 'f-sprite fs-icon_unread_bw');
        newPostsIcon.style.backgroundColor = (bgColor === undefined || bgColor === 'none' ?
                                                '' : bgColor === 'Flickr' ? 
                                                $(document).getElement('body').getComputedStyle('background-color') :
                                                bgColor);
        newPostsIcon.src = images.updating;
        newPostsIcon.height = 10;
        postsAnchor.href = url;
        newPostsIcon.title = title;
        newPostsIcon.alt = title;
        newPostsIcon.style.verticalAlign = 'middle';
        postsAnchor.adopt(newPostsIcon);
        //var injectionPoint = mailAnchor;
        //postsAnchor.inject(injectionPoint, 'before');
	$('NFPN.envelopes').adopt(postsAnchor);
        if (preferences.compactEnvelopeStyle()) {
            postsAnchor.style.position = 'relative';
        } else {
            postsMessageChunk.inject(postsAnchor, 'after');
        }
	newPostsIcon.addEvents({
	    'reset-posts-icon': function(evt) {
    		if (preferences.compactEnvelopeStyle()) {
        		var compactMessageChunk = $(this.id + '.compactMessageChunk');
        		if ($chk(compactMessageChunk)) {
            			compactMessageChunk.destroy();
        		}
    		} else {
        		var postsMessageChunk = $(this.id + '.MessageChunk');
        		postsMessageChunk.setStyle('display', 'none');
        		postsMessageChunk.setStyle('visibility', 'invisible');
		}
    		this.src = images.spaceout;
    		this.set('class', 'f-sprite fs-icon_unread_bw');
    		this.set('height', 16);
	    },
	    'notify-updating': function (evt) {
	    	this.fireEvent('reset-posts-icon'); // TODO: should be synchronous
		this.src = images.updating;
		this.height = 10;
	    },
	    'notify-new-posts': function(evt) {
		this.fireEvent('reset-posts-icon'); // TODO: should be synchronous
		var newPosts = evt.newPosts;
        	if (newPosts > 0) {
			var returnedThreads = evt.returnedThreads;
            		this.set('class', 'f-sprite fs-icon_unread');
            		if (!preferences.compactEnvelopeStyle()) { // regular FlickrMail style
                		var postsMessageChunk = $(this.id + '.MessageChunk');
                		postsMessageChunk.getElement('b').set('html', newPosts.toLocaleString() + (returnedThreads === 'true' ? '+' : ''));
                		postsMessageChunk.set('style', '');
            		} else { // shorter notation
                		var compactMessageChunk = $(this.id + '.compactMessageChunk');
                		var anchor = this.getParent('a');
                		if (!$chk(compactMessageChunk)) {
                    			compactMessageChunk = new Element('span', {
                        			id: this.id + '.compactMessageChunk',
                        			styles: {
                            				zIndex: '2',
                            				position: 'absolute',
                            				right: '0',
                            				top: '0',
                            				fontSize: '80%', // not working in FF??
                            				fontWeight: 'bold',
                            				color: 'black'
                        			}
                    			}).inject(anchor);
                		}
                		compactMessageChunk.empty();
                		compactMessageChunk.set('html', newPosts + (returnedThreads === 'true' ? '+' : ''));
                		compactMessageChunk.title = this.title;
            		}
        	} else {
		}
	    },
	    'notify-error': function(evt) {
        	this.src = images.error;
        	this.height = 16;
        	this.title = this.title + " - " + evt;
	    }
	});
	return newPostsIcon;
    } catch (e) {
        GM_log("error creating mail icon");
    }
}

function markThreadLocked(prefix, id) {
    try {
       var threadIcon = $('NFPN.' + prefix + '.' + id);
       threadIcon.src = images.lock;
       threadIcon.height = 16;
       threadIcon.title = threadIcon.get('title') + " - (locked)";
    } catch (e) {
       GM_log("error showing lock: " + e);
    }
}

function reportMissingObject(data) { 
    var type = data.type;
    var url = data.url;
    var key = data.key;
    var html = data.html ? data.html : url;

    var mailIcon = getMessageIcon();
    if ($chk(mailIcon)) {
        //var entryPoint = mailIcon.getParent('td');
	//if (!$chk(entryPoint)) {
		entryPoint = $('head-status');
	//}
        if ($chk(entryPoint)) {
            var pinky = new Element('div', {
                styles: {
                    backgroundColor: 'pink'
                }
            }).inject(entryPoint);
            pinky.adopt(
                new Element('span', {
                    html: 'Error! found a key for a ' + type + ' for '
                }),
                new Element('a', {
                    href: url,
                    html: html ? html : '&lt;missing&gt;'
                }),
                new Element('span', {
                    html: ' but no valid data '
                }),
                new Element('a', {
                    href: '#',
                    html: '(remove key)',
                    events: {
                        click: function (evt) {
                            evt.preventDefault();
                            GM_deleteValue(key);
                            $(evt.target).getParent('div').dispose();
                        }
                    }
                })
            );
                            
        } else {
            GM_log("error: no entrypoint for error reporting");
        }
    } else {
        GM_log("error: no mail icon found!");
    }
}

function checkForNewSomethingInAnything(data) {
	var onThisPage = false;
	var error = false;

	var debug = data.debug;
	var initData = data.initData;
	var storedInfo = data.storedInfo;
	var delayed = data.delayed;
	var matchUrl = data.matchUrl;
	var linkUrl = $chk(data.linkUrl) ? data.linkUrl : matchUrl;
	var subItemUrl = data.subItemUrl;
	
	var gm_keyPostfix = initData.gm_keyPostfix;
	var idLabel = initData.identifier;

	try {
		if (!$chk(storedInfo)) {
			GM_log("missing stored info for " + gm_keyPostfix + ": " + storedInfo);
			return { stopProcessing: false };
		}
		if (!$chk(storedInfo[idLabel])) {
			GM_log("missing identification for " + gm_keyPostfix + "." + idLabel + ": " + storedInfo[idLabel]);
			return { stopProcessing: false };
		}
		// skip if we're on the watched page itself => the processSomethingPage will handle the envelope
		if (location.href.match(matchUrl)) {
			if ($chk(subItemUrl)) {
				if (!location.href.match(subItemUrl)) {
					onThisPage = true;
				}
			} else {
				onThisPage = true;
			}
		}
		if (storedInfo[initData.error] == 'true' || storedInfo[initData.error] == true) {
			error = true;
		}
		var update = false;
		if ($chk($('NFPN.' + gm_keyPostfix + '.' + storedInfo[idLabel]))) { // already there => update
			update = true;
		}
		if (update) { // already present
			if (onThisPage) {
				return { stopProcessing: true };
			} else {
				$('NFPN.' + gm_keyPostfix + '.' + storedInfo[idLabel]).fireEvent('notify-updating');
				return { stopProcessing: false };
			}
		} else {
                        var title = data.title;
			if (!$chk(title)) {
				title = storedInfo.title + ' ';
                        	if ($chk(initData.titleTemplate)) {
                            	    title = initData.titleTemplate.text
                                    	.replace('%1', storedInfo[initData.titleTemplate.param1])
                                    	.replace('%2', storedInfo[initData.titleTemplate.param2])
					.replace(/<[^>]+>/g, '') // remove HTML markup
                                    	+ ' ';
                            	}
			}
			var newPostsIcon = createPostsIcon(gm_keyPostfix, 
				storedInfo[idLabel],
				linkUrl,
				gm_keyPostfix + ": " + title,
				storedInfo.bgColor);
			newPostsIcon.fireEvent('notify-updating');
			if (onThisPage) {
				return { stopProcessing: true };
			}
			if (delayed && storedInfo[initData.error] !== 'true' && storedInfo[initData.error] !== true) {
				newPostsIcon.fireEvent('notify-new-posts', {
					newPosts: storedInfo[initData.newitems],
					returnedThreads: storedInfo.returnedThreads});
				return { stopProcessing: true };
			}
		}
	} catch (e) {
		GM_log("error checking new items in " + gm_keyPostfix + " - " + storedInfo[idLabel] + ": " + e);
	}
	return { stopProcessing: false }; 
}

// is only called from the main checkForNewPosts loop
function checkForNewPostsInGroup(groupInfo, delayed, helpforum) {
    try {
        var gm_keyPostfix = (helpforum ? "Helpforum" : "Group");
        var groupDiscussionUrl = (helpforum ? 
            "http://www.flickr.com/help/forum/" + groupInfo.groupname + "/" : 
            "http://www.flickr.com/groups/" + groupInfo.groupname + "/discuss/");

	var debug = false; //groupInfo.groupname == '747039@N23';
    	if (debug) GM_log(["DEBUG: checking posts in group " + groupInfo.groupname,
    		"id=" + groupInfo[preferencesInitData.group.identifier],
    		"URL=" + groupDiscussionUrl,
		"storedInfo.items=" + groupInfo['replies'],
		"storedInfo.newItems=" + groupInfo['newPosts'],
		"delayed=" + delayed
	].join('\n'));

        var skipPage = checkForNewSomethingInAnything({
    	    initData: helpforum ? preferencesInitData.helpforum : preferencesInitData.group,
	    storedInfo: groupInfo,
    	    matchUrl: groupDiscussionUrl,
	    subItemUrl: helpforum ?
	    	new RegExp(groupDiscussionUrl + "\\d+|" + groupDiscussionUrl + "\\?page=\\d+") :
	    	new RegExp(groupDiscussionUrl + "\\d+|" + groupDiscussionUrl + "page\\d+"),
	    delayed: delayed
        });

        if (skipPage.stopProcessing) {
	    if (!delayed) GM_log("DEBUG: skipping 'new posts in group'");
    	    return;
        }

        var data = {
	    debug: debug,
            storedInfo: groupInfo, 
	    initData: helpforum ? preferencesInitData.helpforum : preferencesInitData.group,
	    URL: groupDiscussionUrl,
	    countItemsInPage: function (element) {
                var storedThreads = retrieveThreadsFromCache(groupInfo.groupname);
                var groupNewPosts = 0;
                var returnedThreads = 'false';
                element.getElements('table.TopicListing').each( function(table) { // help forums use two tables
                      table.getElements('tr').each( function (row, index) {
                        if (index === 0) {
                            return; // header
                        }
                        var columns = row.getElements('td');
			var threadAnchor = columns[0].getElement('a');
                        var title = threadAnchor.textContent;
                        var topic = threadAnchor.href;
                        var replies = parseInt(columns[2].textContent.replace(/(\.|,)/g, ''), 10);
                        var threadInfo = storedThreads.get(topic);
                        if ($chk(threadInfo)) {
                            if (threadInfo.returnedThread === 'true') {
			    	groupNewPosts +=1;
				returnedThreads = 'true';

                            } else if (threadInfo.newTopic === 'true') {
                                groupNewPosts += replies;
                                groupNewPosts++; // also count the topic starter
				threadInfo.newPosts = replies + 1;
				threadInfo.replies = replies;
                               	GM_storeObject(groupInfo.groupname + '.' + topic, threadInfo);

			    } else {
	                        if (threadInfo.replies < replies) {
        	                    threadInfo.newPosts = (replies - threadInfo.replies);
				    // don't store replies to keep a clean record
				    groupNewPosts += threadInfo.newPosts;
                                    GM_storeObject(groupInfo.groupname + '.' + topic, threadInfo);

	                        } else if (replies < threadInfo.replies) { // someone deleted a comment; record
        	                    returnedThreads = 'true';
				    threadInfo.deletedItems = 'true';
				    threadInfo.replies = replies; // reset - has no effect
                        	    groupNewPosts++;
                                    GM_storeObject(groupInfo.groupname + '.' + topic, threadInfo);
	                        }
			    }
			    // do not attempt to update the envelope of this thread if it has its own envelope
                        } else {
                            // unknown in local storage, means we haven't seen it the last time
                            // could be a thread that returned from pageX
                            // or it could be a completely new topic
                            // if we read the RSS feed, and it contains all the posts of this thread, it's new
                            // (rss feed only contains 10 entries; if posts in thread > 10 => we can't check)
                            // if there are no replies yet, it's also new
                            if (replies === 0) { // new topic
                                groupNewPosts++; // thread start
                                // store as new: if it gets some replies, it would be considered a returned thread
                                GM_storeObject(groupInfo.groupname + '.' + topic, {
                                    groupname: groupInfo.groupname,
                                    topic: topic,
                                    newPosts: replies + 1,
				    replies: replies,
                                    title: title,
                                    newTopic: 'true'
                                });
                            } else {
                                returnedThreads = 'true';
                                groupNewPosts++;
                            }
                        }
                    });
                });
    		return { result: true, items: groupNewPosts, returnedThreads: returnedThreads };
	    },
	    updateInfo: function (element, groupInfo) {
	    }
        };
	newSomethingHtmlCall(data);
    } catch (e) {
        GM_log("error reading " + (helpforum ? " forum " : " group ") + ": " + e);
    }
}

// is only called from the main checkForNewPosts loop
function checkForNewPostsInHelpForum(forumInfo, delayed) {
    checkForNewPostsInGroup(forumInfo, delayed, true);
}

// is only called from the main checkForNewPosts loop
function checkForNewPostsInForumThread(threadInfo, delayed) {
    checkForNewPostsInThread(threadInfo, delayed, true);
}

// is only called from the main checkForNewPosts loop
function checkForNewPostsInThread(threadInfo, delayed, helpforum) {
    try {
    	var gm_keyPostfix = (helpforum ? 'Forumitem' : 'Thread');
    	var threadURL = (helpforum ? 
                            "http://www.flickr.com/help/forum/" + threadInfo.groupname + "/" + threadInfo.topic + "/" :
                            "http://www.flickr.com/groups/" + threadInfo.groupname + "/discuss/" + threadInfo.topic + "/");

    	var skipPage = checkForNewSomethingInAnything({
    		initData: helpforum ? preferencesInitData.forumitem : preferencesInitData.thread,
		storedInfo: threadInfo,
		title: (threadInfo.groupTitle ? threadInfo.groupTitle + ': ' : gm_keyPostfix + ": ") + threadInfo.title,
    		matchUrl: threadURL,
		subItemUrl: new RegExp(threadURL + '\\d+|' + threadURL + '#reply'),
		linkUrl: threadURL + (preferences.gotoLastThreadPage() ? '/lastpage' : ''), 
		delayed: delayed
    	});
	if (skipPage.stopProcessing) {
		if (threadInfo.locked) markThreadLocked(gm_keyPostfix, threadInfo.topic);
		return;
	}
        var data = {
                storedInfo: threadInfo, 
		initData: helpforum ? preferencesInitData.forumitem : preferencesInitData.thread,
		URL: threadURL,
		countItemsInPage: function (element) {
			return countPostsInThreadPage(element);
		},
		updateInfo: function (element, threadInfo) {
                    var title = element.getElement('td[id=GoodStuff]').getElement('h2').get('html');
                    var groupTitle = element.getElement('div[id=Main]').getElement('h1').getElements('a')[1].get('html');
                    
		    threadInfo.groupTitle = groupTitle;
                    // title may have been changed
                    threadInfo.title = title;
		    threadInfo.locked = false;
		    // closed?
		    try {
		        var focus = helpforum ? element.getElement('p.Locked') : element.getElement('p.Focus');
		        if ($chk(focus)) threadInfo.locked = true;
		    } catch (e) {
		        GM_log("error checking lock: " + e);
		    }
		},
		updateIcon: function (threadInfo) {
		    try {
		    	if (threadInfo.locked) markThreadLocked(gm_keyPostfix, threadInfo.topic);
		    } catch (e) {
		    	GM_log("error marking lock: " + e);
		    }
		}
        };
	newSomethingHtmlCall(data);
    } catch (e) {
        GM_log("error reading " + (helpforum ? " forum " : " ") + "thread: " + e);
    }
}

function newSomethingHtmlCall(data) {
	var debug = data.debug;
	var URL = data.URL;
	var storedInfo = data.storedInfo;
	var initData = data.initData;
	var countItemsInPage = data.countItemsInPage;
	var updateInfo = data.updateInfo;
	var updateIcon = $chk(data.updateIcon) ? data.updateIcon : function () {};

        storedInfo[initData.error] = true;
        GM_storeObject('selected' + initData.gm_keyPostfix + '.' + storedInfo[initData.identifier], storedInfo);

        new Request({
            url: URL,
            onSuccess: function (responseText, responseXML) {
                try {
                    var tempDiv = new Element('div', {
                        html: responseText.stripScripts()
                    });
                    var problem = tempDiv.getElement('p.Problem');
                    if ($chk(problem)) {
			GM_log("error reading " + initData.items + " in " + initData.type + ": " + problem.get('html'));
                        $('NFPN.' + initData.gm_keyPostfix + '.' + storedInfo[initData.identifier]).fireEvent('notify-error', problem.get('html'));
                        return;
                    }
                    var countResult = countItemsInPage(tempDiv);
		    if (countResult.result == false) {
		    	$('NFPN.' + initData.gm_keyPostfix + '.' + storedInfo[initData.identifier]).fireEvent('notify-error',countResult.msg);
			return;
		    }
		    // common with ApiCall
		    var items = countResult.items;

		    var firstTime = initData.type != 'group' && storedInfo[initData.items] === undefined;
                    var newsAvailable = firstTime ? 0 : (items - storedInfo[initData.items]);
		    if (items < storedInfo[initData.items]) { // removed items
		    	storedInfo[initData.items] = items;
		    }
		    if (initData.type == 'group' || initData.type == 'helpforum') {
		    	newsAvailable = items;
		    }

                    // don't overwrite the replies!
                    storedInfo[initData.newitems] = newsAvailable;
                    storedInfo[initData.error] = false;
                    
		    updateInfo(tempDiv, storedInfo);

                    GM_storeObject('selected' + initData.gm_keyPostfix + '.' + storedInfo[initData.identifier], storedInfo);
                    $('NFPN.' + initData.gm_keyPostfix + '.' + storedInfo[initData.identifier]).fireEvent('notify-new-posts', {
		    	newPosts: newsAvailable,
			returnedThreads: countResult.returnedThreads});
		    updateIcon(storedInfo);
                } catch (e) {
                    GM_log("exception: " + e);
                }
            },
            onFailure: function (response) {
                try {
                    GM_log("reading " + initData.gm_keyPostfix + "." + storedInfo[initData.identifier] + " failed");
                    $('NFPN.' + initData.gm_keyPostfix + '.' + storedInfo[initData.identifier]).fireEvent('notify-error',response.statusText);
                    // if UNSENT or OPEN, statusText could be empty
                } catch (e) {
		    GM_log("error reading " + initData.type + ": " + e);
                    $('NFPN.' + initData.gm_keyPostfix + '.' + storedInfo[initData.identifier]).fireEvent('notify-error',"failed");
                }
            }
        }).get();
}

function newSomethingApiCall(data) {
	var initData = data.initData;
	var storedInfo = data.storedInfo;
	var itemsExtractor = data.itemsExtractor;
	var updateInfo = data.updateInfo;
	var updateIcon = $chk(data.updateIcon) ? data.updateIcon : function () {}

        storedInfo[initData.error] = true;
        GM_storeObject('selected' + initData.gm_keyPostfix + '.' + storedInfo[initData.identifier], storedInfo);

        new Request({
            url: 'http://www.flickr.com',
            onSuccess: function (responseText, responseXML) {
                try {
                    var result;
                    try {
                        result = JSON.parse(responseText);
                    } catch (e) {
                        result = eval('(' + responseText + ')');
                    }
                    if (result.stat === 'fail') {
			GM_log("error reading " + initData.gm_keyPostfix + " page: " + result.message);
                        $('NFPN.' + initData.gm_keyPostfix + '.' + storedInfo[initData.identifier]).fireEvent('notify-error',result.message);
                        return;
                    }
		    // common with HtmlCall
		    var items = itemsExtractor(result);



		    if (items < storedInfo[initData.items]) { // removed items
		    	storedInfo[initData.items] = items;
		    }
                    var newItems = items - storedInfo[initData.items];
                    
		    
		    
                    // don't overwrite comments!, but remember the new comments
                    storedInfo[initData.newitems] = newItems;
		    storedInfo[initData.error] = false;
		    
		    updateInfo(result, storedInfo);
                    
		    GM_storeObject('selected' + initData.gm_keyPostfix + '.' + storedInfo[initData.identifier], storedInfo);
		    $('NFPN.' + initData.gm_keyPostfix + '.' + storedInfo[initData.identifier]).fireEvent('notify-new-posts', { newPosts: newItems});
		    updateIcon(storedInfo);
                } catch (e) {
                    GM_log("exception: " + e);
                }
            },
            onFailure: function (response) {
                try {
                    GM_log("reading " + initData.gm_keyPostfix + "." + storedInfo[initData.identifier] + " failed");
                    $('NFPN.' + initData.gm_keyPostfix + '.' + storedInfo[initData.identifier]).fireEvent('notify-error',response.statusText);
                    // if UNSENT or OPEN, statusText could be empty
                } catch (e) {
		    GM_log("error reading " + initData.gm_keyPostfix + ": " + e);
                    $('NFPN.' + initData.gm_keyPostfix + '.' + storedInfo[initData.identifier]).fireEvent('notify-error',"failed");
                }
            }
        }).get('/services/rest', data.apiData);
}

// is only called from the main checkForNewPosts loop
function checkForNewCommentsInPhotoPage(photoInfo, delayed) {
    try {
    	var photoURL = "http://www.flickr.com/photos/" + 
                   (photoInfo.ownerId ? photoInfo.ownerId : photoInfo.username) + "/" + photoInfo.photoId + "/";
	var linkURL = photoURL;
    	if (preferences.gotoLastThreadPage()) {
            var page = Math.ceil(photoInfo.comments / 50);
            if (page > 1) {
                linkURL = photoURL + 'page' + page + '/';
            }
    	}
    	var skipPage = checkForNewSomethingInAnything({
    		initData: preferencesInitData.photo,
		storedInfo: photoInfo,
    		matchUrl: photoURL,
		subItemUrl: new RegExp(photoURL + '(?:stats|groups|galleries|favorites)'),
		linkUrl: linkURL,
		delayed: delayed
    	});
    	if (skipPage.stopProcessing) {
	if (!delayed) GM_log("DEBUG: skipping 'new comments in photo'");
		return;
	}
	newSomethingApiCall({
		initData: preferencesInitData.photo,
		storedInfo: photoInfo,
		itemsExtractor: function(result) {
			return result.photo.comments._content;
		},
		updateInfo: function (result, photoInfo) {
                    // title can have been changed
                    photoInfo.title = result.photo.title._content;
                    // store possibly empty owner id
                    photoInfo.ownerId = result.photo.owner.nsid;
                    // username may have been changed
                    photoInfo.username = result.photo.owner.username;
		},
		apiData: {
            		api_key: GM_getMagisterLudi(),
            		auth_hash: GM_getAuthHash(),
            		auth_token: GM_getAuthToken(),
            		format: 'json',
            		method: 'flickr.photos.getInfo',
            		nojsoncallback: 1,
            		photo_id: photoInfo.photoId
		}
	});
    } catch (e) {
        GM_log("error reading photo page: " + e);
    }
}

// is only called from the main checkForNewPosts loop
function checkForNewPhotosInPhotostream(streamInfo, delayed) {
    try {
    	var streamURL = "http://www.flickr.com/photos/" + streamInfo.username + "/";
    	var skipPage = checkForNewSomethingInAnything({
    		initData: preferencesInitData.photostream,
		storedInfo: streamInfo,
    		matchUrl: new RegExp(streamURL + '(?:page\\d+)?/?$'),
		subItemUrl: new RegExp(streamURL + '\\d+'),
		linkUrl: streamURL,
		delayed: delayed
    	});
    	if (skipPage.stopProcessing) {
	if (!delayed) GM_log("DEBUG: skipping 'new photos in stream'");
		return;
	}
	newSomethingApiCall({
		initData: preferencesInitData.photostream,
		storedInfo: streamInfo,
		itemsExtractor: function(result) {
			return result.person.photos.count._content;
		},
		updateInfo: function (result, streamInfo) {
                    // display name can have been changed
                    if (result.person.realname) {
                        streamInfo.title = result.person.realname._content;
                    }
                    if (!$chk(streamInfo.title)) {
                        streamInfo.title = result.person.username._content;
                    }
                    if (!$chk(streamInfo.title)) {
                        streamInfo.title = streamInfo.username;
                    }
		},
		apiData: {
            		api_key: GM_getMagisterLudi(),
            		auth_hash: GM_getAuthHash(),
            		auth_token: GM_getAuthToken(),
            		format: 'json',
            		method: 'flickr.people.getInfo',
            		nojsoncallback: 1,
            		user_id: streamInfo.userNsid
		}
        });
    } catch (e) {
        GM_log("error reading photostream: " + e);
    }
}

// is only called from the main checkForNewPosts loop
function checkForNewMembersInGroup(groupmembersInfo, delayed) {
    try {
        var membersURL = "http://www.flickr.com/groups_members.gne?id=" + groupmembersInfo.groupId;
	var matchURL = "http:\\/\\/www.flickr.com\\/groups_members.gne\\?id=" + groupmembersInfo.groupId;
	if ($chk(groupmembersInfo.groupname)) {
		matchURL = matchURL + "|" + "http:\\/\\/www.flickr.com\\/groups\\/" + groupmembersInfo.groupname + "\\/members\\/";
	}
    	var skipPage = checkForNewSomethingInAnything({
    		initData: preferencesInitData.groupmembers,
		storedInfo: groupmembersInfo,
    		matchUrl: new RegExp(matchURL),
		linkUrl: membersURL,
		delayed: delayed
    	});
    	if (skipPage.stopProcessing) {
	if (!delayed) GM_log("DEBUG: skipping 'new members in group'");
		return;
	}
	newSomethingApiCall({
		initData: preferencesInitData.groupmembers,
		storedInfo: groupmembersInfo,
		itemsExtractor: function(result) {
			return result.members.total;
		},
		updateInfo: function (result, groupmembersInfo) {
		},
		apiData: {
            		api_key: GM_getMagisterLudi(),
            		auth_hash: GM_getAuthHash(),
            		auth_token: GM_getAuthToken(),
            		format: 'json',
            		method: 'flickr.groups.members.getList',
            		nojsoncallback: 1,
            		group_id: groupmembersInfo.groupId,
            		//membertypes: 2, count all members: a new member could already have been promoted
            		per_page: 1
		}
        });
    } catch (e) {
        GM_log("error reading group members: " + e);
    }
}

// is only called from the main checkForNewPosts loop
function checkForNewPhotosInGroupPool(grouppoolInfo, delayed) {
    try {
        var poolURL = "http://www.flickr.com/groups/" + grouppoolInfo.groupname + "/pool/";
    	var skipPage = checkForNewSomethingInAnything({
    		initData: preferencesInitData.grouppool,
		storedInfo: grouppoolInfo,
    		matchUrl: poolURL,
		subItemUrl: new RegExp(poolURL + 'tags'),
		delayed: delayed
    	});
    	if (skipPage.stopProcessing) {
	if (!delayed) GM_log("DEBUG: skipping 'new photos in pool'");
		return;
	}
	newSomethingApiCall({
		initData: preferencesInitData.grouppool,
		storedInfo: grouppoolInfo,
		itemsExtractor: function(result) {
			return result.photos.total;
		},
		updateInfo: function (result, grouppoolInfo) {
		},
		apiData: {
            		api_key: GM_getMagisterLudi(),
            		auth_hash: GM_getAuthHash(),
            		auth_token: GM_getAuthToken(),
            		format: 'json',
            		method: 'flickr.groups.pools.getPhotos',
            		per_page: 1,
            		nojsoncallback: 1,
            		group_id: grouppoolInfo.groupId
		}
        });
    } catch (e) {
        GM_log("error reading photo pool: " + e);
    }
}

// is only called from the main checkForNewPosts loop
function checkForNewReversedContacts(reversedContactsInfo, delayed) {
    try {
        var URL = "http://www.flickr.com/people/" + reversedContactsInfo.username + "/contacts/rev/";
    	var skipPage = checkForNewSomethingInAnything({
    		initData: preferencesInitData.reversedcontacts,
		storedInfo: reversedContactsInfo,
    		matchUrl: URL,
		delayed: delayed
    	});
        if (skipPage.stopProcessing) {
		GM_log("DEBUG: stop processing");
		if (!delayed) GM_log("DEBUG: skipping 'new reversed contacts'");
		return;
	}
	newSomethingHtmlCall({
                storedInfo: reversedContactsInfo, 
		initData: preferencesInitData.reversedcontacts,
		URL: URL,
		countItemsInPage: function (element) {
			return countReversedContacts(element);
		},
		updateInfo: function (element, reversedContactsInfo) {
		}
        });
    } catch (e) {
        GM_log("error reading reversed contacts: " + e);
    }
}

function checkForNewPendingSomething(pendingSomethingInfo, initData, URL, title, delayed, countPendingSomethingFunction) {
    try {
    	var skipPage = checkForNewSomethingInAnything({
    		initData: initData,
		storedInfo: pendingSomethingInfo,
    		matchUrl: URL,
		delayed: delayed
    	});
	if (skipPage.stopProcessing) {
	if (!delayed) GM_log("DEBUG: skipping 'new pending " + initData.type + "'");
		return;
	}
	newSomethingHtmlCall({
                storedInfo: pendingSomethingInfo, 
		initData: initData,
		URL: URL,
		countItemsInPage: function (element) {
			return countPendingSomethingFunction(element);
		},
		updateInfo: function (element, pendingSomentingInfo) {
		}
        });
    } catch (e) {
        GM_log("error reading " + initData.text + ": " + e);
    }
}

// is only called from the main checkForNewPosts loop
function checkForNewPendingItems(pendingItemsInfo, delayed) {
	var title = 'Pending items: ' + pendingItemsInfo.title;
        var URL = "http://www.flickr.com/groups/" + pendingItemsInfo.groupname + "/admin/pending/";
	checkForNewPendingSomething(pendingItemsInfo, preferencesInitData.pendingitems, URL, title, delayed, countPendingItems);
}

// is only called from the main checkForNewPosts loop
function checkForNewPendingTestimonials(pendingTestimonialsInfo, delayed) {
	var title = 'Pending testimonials: ' + pendingTestimonialsInfo.realname;
	var URL = "http://www.flickr.com/testimonials_manage.gne";
	checkForNewPendingSomething(pendingTestimonialsInfo, preferencesInitData.pendingtestimonials, URL, title, delayed, countPendingTestimonials);
}

// is only called from the main checkForNewPosts loop
function checkForNewAppComments(appInfo, delayed) {
    try {
        var URL = "http://www.flickr.com/services/apps/" + appInfo.appId + "/";
    	var skipPage = checkForNewSomethingInAnything({
    		initData: preferencesInitData.application,
		storedInfo: appInfo,
    		matchUrl: URL,
		subItemUrl: new RegExp(URL + "key"),
		delayed: delayed
    	});
        if (skipPage.stopProcessing) {
	if (!delayed) GM_log("DEBUG: skipping 'new comments on app'");
		return;
	}
	newSomethingHtmlCall({
                storedInfo: appInfo, 
		initData: preferencesInitData.application,
		URL: URL,
		countItemsInPage: function (element) {
			return countCommentsInAppPage(element);
		},
		updateInfo: function (element, pendingSomentingInfo) {
		}
        });
    } catch (e) {
        GM_log("error reading app comments: " + e);
    }
}

// is only called from the main checkForNewPosts loop
function checkForNewSetComments(setInfo, delayed) {
    try {
        var setURL = "http://www.flickr.com/photos/" + setInfo.username + "/sets/" + setInfo.setId + "/comments/";
    	var skipPage = checkForNewSomethingInAnything({
    		initData: preferencesInitData.set,
		storedInfo: setInfo,
    		matchUrl: setURL,
		delayed: delayed
    	});
    	if (skipPage.stopProcessing) {
	if (!delayed) GM_log("DEBUG: skipping 'new comments in set'");
		return;
	}
	newSomethingApiCall({
		initData: preferencesInitData.set,
		storedInfo: setInfo,
		itemsExtractor: function(result) {
                    	return result.comments.comment.length;
		},
		updateInfo: function (result, setInfo) {
		},
		apiData: {
            api_key: GM_getMagisterLudi(),
            auth_hash: GM_getAuthHash(),
            auth_token: GM_getAuthToken(),
            format: 'json',
            method: 'flickr.photosets.comments.getList',
            per_page: 500,
            nojsoncallback: 1,
            photoset_id: setInfo.setId
		}
        });
                       /* 'Set: ' + setInfo.title + '(by ' + setInfo.realname + ')', */
    } catch (e) {
        GM_log("error reading photo set: " + e);
    }
}

var watchTypes = {
    group: {
        add: 'add this group to the Flickr Notifications watchlist',
        remove: 'remove this group from the Flickr Notifications watchlist',
        gm_keyPostfix: preferencesInitData.group.gm_keyPostfix,
        discussPage: true,
        id: undefined // groupname
    },
    helpforum: {
        add: 'add this help forum to the Flickr Notifications watchlist',
        remove: 'remove this help forum from the Flickr Notifications watchlist',
        gm_keyPostfix: preferencesInitData.helpforum.gm_keyPostfix,
        helpForum: true,
        id: undefined // groupname
    },
    thread: {
        add: 'add this thread to the Flickr Notifications watchlist',
        remove: 'remove this thread from the Flickr Notifications watchlist',
        gm_keyPostfix: preferencesInitData.thread.gm_keyPostfix,
        discussThread: true,
        id: undefined // topic
    },
    forumitem: {
        add: 'add this help item to the Flickr Notifications watchlist',
        remove: 'remove this help item from the Flickr Notifications watchlist',
        gm_keyPostfix: preferencesInitData.forumitem.gm_keyPostfix,
        forumItem: true,
        id: undefined // topic
    },
    photo: {
        add: 'add this photo to the Flickr Notifications watchlist',
        remove: 'remove this photo from the Flickr Notifications watchlist',
        gm_keyPostfix: preferencesInitData.photo.gm_keyPostfix,
        photoPage: true,
        id: undefined // photoId
    },
    photostream: {
        add: 'add this photostream to the Flickr Notifications watchlist',
        remove: 'remove this photostream from the Flickr Notifications watchlist',
        gm_keyPostfix: preferencesInitData.photostream.gm_keyPostfix,
        photoStream: true,
        id: undefined // username
    },
    groupmembers: {
        add: 'add this member page to the Flickr Notifications watchlist',
        remove: 'remove this member page from the Flickr Notifications watchlist',
        gm_keyPostfix: preferencesInitData.groupmembers.gm_keyPostfix,
        groupmembers: true,
        id: undefined // groupId
    },
    grouppool: {
        add: 'add this group pool page to the Flickr Notifications watchlist',
        remove: 'remove this group pool page from the Flickr Notifications watchlist',
        gm_keyPostfix: preferencesInitData.grouppool.gm_keyPostfix,
        grouppool: true,
        id: undefined // groupId
    },
    reversedcontacts: {
        add: 'add this reversed contacts page to the Flickr Notifications watchlist',
        remove: 'remove this reversed contacts page from the Flickr Notifications watchlist',
        gm_keyPostfix: preferencesInitData.reversedcontacts.gm_keyPostfix,
        reversedcontacts: true,
        id: undefined // usernsid
    },
    pendingitems: {
        add: 'add this pending items page to the Flickr Notifications watchlist',
        remove: 'remove this pending items page from the Flickr Notifications watchlist',
        gm_keyPostfix: preferencesInitData.pendingitems.gm_keyPostfix,
        pendingitems: true,
        id: undefined // groupname
    },
    application: {
        add: 'add this application page to the Flickr Notifications watchlist',
        remove: 'remove this application page from the Flickr Notifications watchlist',
        gm_keyPostfix: preferencesInitData.application.gm_keyPostfix,
        application: true,
        id: undefined // application id
    },
    set: {
        add: 'add this set to the Flickr Notifications watchlist',
        remove: 'remove this set from the Flickr Notifications watchlist',
        gm_keyPostfix: preferencesInitData.set.gm_keyPostfix,
        set: true,
        id: undefined // set id
    },
    pendingtestimonials: {
    	add: 'add this testimonials page to the Flickr Notifications watchlist',
	remove: 'remove this testimonials page from the Flickr Notifications watchlist',
	gm_keyPostfix: preferencesInitData.pendingtestimonials.gm_keyPostfix,
	pendingtestimonials: true,
	id: undefined // usernsid
    }
};

function addMinusIcon(watchType) {
        if ($chk($('NFPN.plusmin.icon'))) { // already there
            return;
        }
        var minEntryPoint = getUnreadMessageChunk();
        if (!$chk(minEntryPoint)) {
            minEntryPoint = getMessageIcon().getParent();
        }
        new Element('img', {
            src: images.minus,
            id: 'NFPN.plusmin.icon',
            height: '10px',
            styles: {
                cursor: 'pointer'
            },
            title: watchType.remove,
            events: {
                click: function() {
                    this.dispose();
                    GM_deleteValue('selected' + watchType.gm_keyPostfix + '.' + watchType.id);
                    if (watchType.discussPage === true || watchType.helpForum === true) {
                        removeThreadsFromCache(watchType.id);
                    }
                    addPlusIcon(watchType);
                    // the following code could error out if we are on an item that has a key set, but with an invalid object
                    $('NFPN.' + watchType.gm_keyPostfix + '.' + watchType.id).dispose();
                    var messageChunk = $('NFPN.' + watchType.gm_keyPostfix + '.' + watchType.id + '.MessageChunk');
                    if ($chk(messageChunk)) {
                        messageChunk.dispose();
                    }
                }
            }
        }).inject(minEntryPoint, 'after'); 
}

function addPlusIcon(watchType) {
        if ($chk($('NFPN.plusmin.icon'))) { // already there
            return;
        }
        var plusEntryPoint = getUnreadMessageChunk();
        if (!$chk(plusEntryPoint)) {
            plusEntryPoint = getMessageIcon().getParent();
        }
        new Element('img', {
            src: images.plus,
            id: 'NFPN.plusmin.icon',
            height: '10px',
            styles: {
                cursor: 'pointer'
            },
            title: watchType.add,
            events: {
                click: function() {
                    try {
		    	var processPageFunction = undefined;
                        if (watchType.discussPage === true) {
                            processPageFunction = processDiscussPage;
                            var titleHeader = $('cattington_outer');
                            if ($chk(titleHeader)) {
                                var groupTitle = titleHeader.getElement('h1 a').get('html');
                            } else { // sponsored group?
                                groupTitle = $('sg_group_title_bg').get('html');
                            }
                            var groupId = GM_getGroupId();
                            GM_storeObject('selectedGroup.' + watchType.id, { 
                                                            groupname: watchType.id, 
                                                            groupId: groupId,
                                                            title: groupTitle,
                                                            groupId: groupId
                                                          });
                            watchType.title = groupTitle;

                        } else if (watchType.helpForum === true) {
                            processPageFunction = processHelpForumPage;
                            var forumTitle = $('Tertiary').textContent.replace(/\n[^]*$/im, '');
                            GM_storeObject('selectedHelpforum.' + watchType.id, {
                                                                groupname: watchType.id,
                                                                title: forumTitle
                                                            });
                            watchType.title = forumTitle;

                        } else if (watchType.discussThread === true) {
                            processPageFunction = processThreadPage;
                            var threadTitle = $('GoodStuff').getElement('h2').get('html');
                            var titleHeader = $('Tertiary');
                            if ($chk(titleHeader)) {
                                var groupTitle = titleHeader.getElements('a')[1].get('html');
                            } else { // sponsored group?
                                groupTitle = $('sg_group_title_bg').get('html');
                            }
                            var count = countPostsInThreadPage();
			    if (count.result == false) {
			    	$('NFPN.' + watchType.gm_keyPostfix + '.' + watchType.id).fireEvent('notify-error',count.msg);
				return;
			    }
                            var bgColor;
                            var metaInfo = GM_getObject('selectedGroup.' + watchType.groupname);
                            if (metaInfo.key_present) {
                                var selectedGroup = metaInfo.value;
                                if (selectedGroup) {
                                    bgColor = selectedGroup.bgColor;
                                }
                            }
                            GM_storeObject('selectedThread.' + watchType.id, { 
                                                            topic: watchType.id, 
                                                            groupname: watchType.groupname, 
                                                            groupTitle: groupTitle,
                                                            groupId: groupId,
                                                            title: threadTitle,
                                                            replies: count.items,
                                                            bgColor: bgColor
                                                        });
                            watchType.title = threadTitle;

                        } else if (watchType.forumItem === true) {
                            processPageFunction = processForumItemPage;
                            var threadTitle = $('GoodStuff').getElement('h2').get('html');
                            var count = countPostsInThreadPage(); // resuse
			    if (count.result == false) {
			    	$('NFPN.' + watchType.gm_keyPostfix + '.' + watchType.id).fireEvent('notify-error',count.msg);
				return;
			    }
                            var forumTitle = getMain().getElements('h1 a')[1].textContent.replace(/\n[^]*$/im, '');
                            GM_storeObject('selectedForumitem.' + watchType.id, { 
                                                            topic: watchType.id, 
                                                            groupTitle: forumTitle,
                                                            groupname: watchType.groupname, 
                                                            title: threadTitle,
                                                            replies: count.items
                                                        });
                            watchType.title = threadTitle;

                        } else if (watchType.photoPage === true) {
                            processPageFunction = processPhotoPage;
                            var main = getMain();
                            var photoTitle = main.getElement('h1').get('html');
                            var count = countCommentsInPhotoPage();
			    if (count.result == false) {
			    	$('NFPN.' + watchType.gm_keyPostfix + '.' + watchType.id).fireEvent('notify-error',count.msg);
				return;
			    }
                            GM_storeObject('selectedPhoto.' + watchType.id, { 
                                                            photoId: watchType.id, 
                                                            username: watchType.username,
                                                            title: photoTitle,
                                                            comments: count.items
                                                        });
                            watchType.title = photoTitle;
                        } else if (watchType.photoStream === true) {
                            processPageFunction = processPhotostreamPage;
                            var count = countPhotosInPhotostream();
			    if (count.result == false) {
			    	$('NFPN.' + watchType.gm_keyPostfix + '.' + watchType.id).fireEvent('notify-error',count.msg);
				return;
			    }
                            GM_storeObject('selectedPhotostream.' + watchType.id, {
                                                            username: watchType.username,
                                                            userNsid: watchType.id,
                                                            title: watchType.username, // temporary
                                                            photos: count.items
                                                        });
                            watchType.title = watchType.username;
                        } else if (watchType.groupmembers === true) {
                            processPageFunction = processGroupMembersPage;
                            var count = countMembersInGroup();
			    if (count.result == false) {
			    	$('NFPN.' + watchType.gm_keyPostfix + '.' + watchType.id).fireEvent('notify-error',count.msg);
				return;
			    }
                            GM_storeObject('selectedGroupmembers.' + watchType.id, {
                                                            groupId: watchType.id,
                                                            title: watchType.title,
                                                            members: count.items
                                                        });
                        } else if (watchType.grouppool === true) {
                            processPageFunction = processGroupPoolPage;
                            var count = countPhotosInGroup();
			    if (count.result == false) {
			    	$('NFPN.' + watchType.gm_keyPostfix + '.' + watchType.id).fireEvent('notify-error',count.msg);
				return;
			    }
                            var reGroupnameMatch = /.*flickr.com\/groups\/([^\/]*)\//;
                            var groupname = reGroupnameMatch.exec(document.location.href)[1];
                            GM_storeObject('selectedGrouppool.' + watchType.id, {
                                                            groupId: watchType.id,
                                                            groupname: groupname,
                                                            title: watchType.title,
                                                            photos: count.items
                                                        });
                        } else if (watchType.reversedcontacts === true) {
                            processPageFunction = processReversedContactsPage;
                            var count = countReversedContacts();
			    if (count.result == false) {
			    	$('NFPN.' + watchType.gm_keyPostfix + '.' + watchType.id).fireEvent('notify-error',count.msg);
				return;
			    }
                            var username = /www.flickr.com\/people\/([^\/]+)\/contacts\/rev/.exec(document.location.href)[1];
                            GM_storeObject('selectedReversedcontacts.' + watchType.id, {
                                                            userNsid: watchType.id,
                                                            username: username,
                                                            title: watchType.title,
                                                            members: count.items
                                                        });
                        } else if (watchType.pendingitems === true) {
                            processPageFunction = processPendingItemsPage;
                            var count = countPendingItems();
			    if (count.result == false) {
			    	$('NFPN.' + watchType.gm_keyPostfix + '.' + watchType.id).fireEvent('notify-error',count.msg);
				return;
			    }
                            GM_storeObject('selectedPendingitems.' + watchType.id, {
                                                            groupname: watchType.id,
                                                            title: watchType.title,
                                                            items: count.items
                                                        });
                        } else if (watchType.application === true) {
                            processPageFunction = processAppPage;
                            var count = countCommentsInAppPage();
			    if (count.result == false) {
			    	$('NFPN.' + watchType.gm_keyPostfix + '.' + watchType.id).fireEvent('notify-error',count.msg);
				return;
			    }
                            GM_storeObject('selectedApp.' + watchType.id, {
                                                            appId: watchType.id,
                                                            username: watchType.username,
                                                            title: watchType.title,
                                                            comments: count.items
                                                        });
                        } else if (watchType.set === true) {
                            processPageFunction = processSetPage;
                            var count = countCommentsInSetPage();
			    if (count.result == false) {
			    	$('NFPN.' + watchType.gm_keyPostfix + '.' + watchType.id).fireEvent('notify-error',count.msg);
				return;
			    }
                            GM_storeObject('selectedSet.' + watchType.id, {
                                                            setId: watchType.id,
                                                            username: watchType.username,
                                                            realname: watchType.realname,
                                                            title: watchType.title,
                                                            comments: count.items
                                                        });
                        } else if (watchType.pendingtestimonials === true) {
			    processPageFunction = processPendingTestimonialsPage;
                            var count = countPendingTestimonials();
			    if (count.result == false) {
			    	$('NFPN.' + watchType.gm_keyPostfix + '.' + watchType.id).fireEvent('notify-error',count.msg);
				return;
			    }
                            GM_storeObject('selectedPendingtestimonials.' + watchType.id, {
                                                            userNsid: watchType.id,
							    realname: watchType.title,
                                                            title: watchType.title,
                                                            items: count.items
                                                        });
                        }

                        this.dispose();
                        
			var newPostsIcon = createPostsIcon(watchType.gm_keyPostfix, watchType.id, "javascript:void(0);", watchType.title);
			newPostsIcon.src = images.ok; // for some reason, the ok image is not shown
			newPostsIcon.height = 16;
			addMinusIcon(watchType);

			try {
				processPageFunction();
			} catch (e) {
				$('NFPN.' + watchType.gm_keyPostfix + '.' + watchType.id).fireEvent('notify-error',e);
			}
                    
		    } catch (e) {
                        this.set('src', images.error);
                        this.set('title', e);
                        this.removeEvent('click');
                        return;
                    }
                }
            }
        }).inject(plusEntryPoint, 'after');
}

function addPlusMinIconOnDiscussPage(data) {
        var topic = data.topic;
        var groupname = data.groupname;
        var helpforum = data.helpforum;
        var cell = data.cell;
        var watchType = helpforum ? watchTypes.forumitem : watchTypes.thread;
	if ($chk($('NFPN.plusmin.icon-' + topic))) { // already present
                return;
        }

        var threadInfo = GM_getObject('selected' + watchType.gm_keyPostfix + '.' + topic);

    if ($chk(threadInfo) && threadInfo.key_present) {
        new Element('img', {
            src: images.minus,
            id: 'NFPN.plusmin.icon-' + topic,
            height: '8px',
            styles: {
                cursor: 'pointer'
            },
            title: watchType.remove,
            events: {
                click: function(evt) {
		    var target = $(evt.target);
                    var topic = target.get('id').replace('NFPN.plusmin.icon-', '');
                    target.dispose();
                    GM_deleteValue('selected' + watchType.gm_keyPostfix + '.' + topic);
                    addPlusMinIconOnDiscussPage(data);
                    // the following code could error out if we are on an item that has a key set, but with an invalid object
                    $('NFPN.' + watchType.gm_keyPostfix + '.' + topic).dispose();
                    var messageChunk = $('NFPN.' + watchType.gm_keyPostfix + '.' + topic + '.MessageChunk');
                    if ($chk(messageChunk)) {
                        messageChunk.dispose();
                    }
                }
            }
        }).inject(cell, 'top');
    } else {
        new Element('img', {
            src: images.plus,
            id: 'NFPN.plusmin.icon-' + topic,
            height: '8px',
            styles: {
                cursor: 'pointer'
            }, 
            title: watchType.add,
            events: {
                click: function() {
                    var titleColumn = 0;
                    var headers = this.getParent('table').getElement('tr').getElements('th,td'); // Discuss page uses th, forum uses td
                    if (headers[0].get('html').match(/UCP-ng/)) {
                            titleColumn = 1;
                    }
		     var threadTitle = this.getParent('tr').getElements('td')[titleColumn].getElement('a').textContent;
                    var replies = parseInt(this.getParent('tr').getElements('td')[titleColumn+2].textContent.match(/^(\d+)/)[1]);
                    var groupTitle = $$('h1 a')[0].textContent;
                    var groupname =
                                document.location.href.match(helpforum ?
                                        /.*flickr.com\/help\/forum\/([^\/]*)/ :
                                        /.*flickr.com\/groups\/([^\/]+)/)[1];
                    var bgColor;
                    var metaInfo = GM_getObject(helpforum ? 'selectedHelpforum.' : 'selectedGroup.' + groupname);
                    if (metaInfo.key_present) {
                        var selectedGroup = metaInfo.value;
                        if (selectedGroup) {
                                    bgColor = selectedGroup.bgColor;
                        }
                    }

                    GM_storeObject((helpforum ? 'selectedForumitem.' : 'selectedThread.') + topic, {
                                                            topic: topic,
                                                            groupname: groupname,
                                                            groupTitle: groupTitle,
                                                            groupId: GM_getGroupId(),
                                                            title: threadTitle,
                                                            replies: replies,
                                                            bgColor: bgColor
                                                        });
                    watchType.title = threadTitle;

                    this.dispose();
			var newPostsIcon = createPostsIcon(watchType.gm_keyPostfix, topic, "javascript:void(0);", threadTitle);
			newPostsIcon.src = images.ok;
			newPostsIcon.height = 16;
                    addPlusMinIconOnDiscussPage(data);
                }
            }
        }).inject(cell, 'top');
    }
}

var MAX_DELAY_TIME = 60 * 60 * 24; // * 7; // 1 week
var MIN_DELAY_TIME = 30; // in seconds

function checkForNewPosts() {
    var keyValues = GM_listValues();
    //keyValues.sort();
    $each(preferencesInitData, function (initData) {
        try {
	    var debug = false; // initData.type === 'group';
            var needsSeparator = false;
            var reKey = new RegExp('^selected' + initData.gm_keyPostfix + '\\.');
            var checkDelayForType = preferences.checkDelayFor(initData.gm_keyPostfix);
            if (!$chk(checkDelayForType) || checkDelayForType === -1) {
                checkDelayForType = preferences.defaultCheckDelay();
            }
	    checkDelayForType = Math.min(checkDelayForType, MAX_DELAY_TIME);
	    checkDelayForType = Math.max(checkDelayForType, MIN_DELAY_TIME);
            var now = new Date().getTime();
	    // bug fix: we should not check on the type's lastChecked time, but on the item's lastChecked time
            GM_deleteValue(initData.gm_keyPostfix + ".lastChecked");
            $each(keyValues, function (key) {
                if (key.match(reKey)) {
                    var metaInfo = GM_getObject(key);
                    if (metaInfo.key_present) { // always true
                        var storedInfo = metaInfo.value;
			// there could be selectedSomething.undefined keys in there!
			//if (debug) GM_log("DEBUG: key=" + key.replace(reKey, ''));
                        if (key.replace(reKey, '') != 'undefined' && $chk(storedInfo)) {
                            var itemCheckDelay = storedInfo.checkDelay;
                            if (itemCheckDelay === undefined || itemCheckDelay == -1) {
                                itemCheckDelay = checkDelayForType; // revert to type's default
                            } else {
                                itemCheckDelay = parseInt(itemCheckDelay, 10);
                            }
			    // bug fix for 4h = 86400s => 4h = 14400
			    if (itemCheckDelay == 86400) {
			    	itemCheckDelay = 14400;
				storedInfo.checkDelay = 14400;
                                GM_storeObject(key, storedInfo);
			    }
			    itemCheckDelay = Math.min(itemCheckDelay, MAX_DELAY_TIME);
			    itemCheckDelay = Math.max(itemCheckDelay, MIN_DELAY_TIME);
			    var itemLastCheck = storedInfo.lastChecked;
			    if (!$chk(itemLastCheck)) {
			    	var delayed = false;
			    } else {
				var elapsedTimeForItem = now - parseInt(itemLastCheck, 10);
                            	delayed = (elapsedTimeForItem/1000 < itemCheckDelay);
			    }
                            if (!delayed) {
                                storedInfo.lastChecked = now.toString();
				storedInfo[initData.error] == true;
                                GM_storeObject(key, storedInfo);
                            }
                            try {
                                initData.checkForNewCallback(storedInfo, delayed);
                            	needsSeparator = true;
				return; // only fall through for missing, or invalid, data
                            } catch (e) {
                                GM_log("error running checkForNewCallback: " + e + " - " + key);
                            }
                        } 
		    }
                    var type = initData.type;
                    var url;
                    var html;
		    try {
                        if (key.match(/selectedPhoto\./)) {
                                var photoId = /selectedPhoto.(.*)/.exec(key)[1]; // NOT \d+ : could be 'undefined'
				if (photoId.match(/^\d+$/)) photoId = photoId.match(/^\d+$/)[1];
                                url = 'http://www.flickr.com/photo.gne?id=' + photoId;
                            
                        } else if (key.match(/selectedPhotostream\./)) {
                                var userNsid = /selectedPhotostream\.(.*)/.exec(key)[1];
                                var user = resolveUsername(userNsid);
                                url = 'http://www.flickr.com/photos/' + user.username + '/';
                            
                        } else if (key.match(/selectedThread\./)) {
                                threadId = /selectedThread\.(.*)/.exec(key)[1];
                                // we need missing 'groupname'
                    		if (metaInfo.key_present) {
                        		var threadInfo = metaInfo.value;
                            		url = "http://www.flickr.com/groups/" + threadInfo.groupname + "/discuss/" + threadInfo.topic + "/";
				} else {
					url = "http://www.flickr.com/"
				}
                        } else if (key.match(/selectedGroup\./)) {
                                var groupname = /selectedGroup\.(.*)/.exec(key)[1];
                                url = 'http://www.flickr.com/groups/' + groupname + '/discuss/';

                        } else if (key.match(/selectedHelpforum\./)) {
                                var groupname = /selectedHelpforum\.(.*)/.exec(key)[1];
                                url = 'http://www.flickr.com/help/forum/' + groupname + '/';

                        } else if (key.match(/selectedForumitem\./)) {
                                var topicId = /selectedForumitem.(.*)/.exec(key)[1];
                                // we don't have a group name, but all topics are available in all languages :)
                                // default to 'en-us'
                                url = 'http://www.flickr.com/help/forum/en-us/' + topicId + '/';
                            
                        } else if (key.match(/selectedGroupmembers\./)) {
                                var groupid = /selectedGroupmembers\.(.*)/.exec(key)[1];
                                url = 'http://www.flickr.com/groups_members.gne?id=' + groupid;

                        } else if (key.match(/selectedGrouppool\./)) {
                                var groupname = /selectedGrouppool\.(.*)/.exec(key)[1];
                                url = 'http://www.flickr.com/groups/' + groupname + '/pool/';

                        } else if (key.match(/selectedReversedcontacts\./)) {
                                var userNsid = /selectedReversedcontacts\.(.*)/.exec(key) [1];
                                var user = resolveUsername(userNsid);
                                url = 'http://www.flickr.com/people/' + user.username + '/contacts/rev/';

                        } else if (key.match(/selectedPendingitems\./)) {
                                var groupname = /selectedPendingitems\.(.*)/.exec(key)[1];
                                url = 'http://www.flickr.com/groups/' + groupname + '/admin/pending/';

                        } else if (key.match(/selectedApp\./)) {
                                var appId = /selectedApp\.(.*)/.exec(key)[1];
				if (appId.match(/^\d+/)) {
                                	url = 'http://www.flickr.com/services/apps/' + appId + '/';
				}
                    		if (metaInfo.key_present) {
                        		var appInfo = metaInfo.value;
					if (url == undefined) {
						url = 'http://www.flickr.com/services/apps/' + appInfo.appId + '/'
					}
                                	html = initData.titleTemplate.text
                                                       .replace('%1', appInfo.title)
                                                       .replace('%2', appInfo.username);
                            	}
                                if (url == undefined)
					url = 'http://www.flickr.com/services/apps/' + appId + '/';
                            
                        } else if (key.match(/selectedSet\./)) {
                                var setId = /selectedSet\.(.*)/.exec(key)[1];
                                url = constructSetUrl(setId);

                        } else if (key.match(/selectedPendingtestimonials\./)) {
			    	url = 'http://www.flickr.com/testimonials_manage.gne';
			}
		    } catch (e) {
		    }

                    reportMissingObject({ 
                            type: type,
                            url: url,
                            html: html,
                            key: key
                    });
                }
            });
            //var mailIcon = getMessageIcon();
            if (needsSeparator) {
                // separator
                new Element('span', {
                    html: ' ',
                    styles: {
                        'border-right': '1px dotted #BABABA',
                        margin: '0 1px 0 0px',
                        padding: '0 0 0 0',
			width: 1
                    }
                }).inject($('NFPN.envelopes')); //mailIcon.getParent('a'), 'before');
                new Element('span', {
                    html: ' ',
                    styles: {
                        margin: '0 0 0 1px',
                        padding: '0 0 0 0',
			width: 1
                    }
                }).inject($('NFPN.envelopes'));//mailIcon.getParent('a'), 'before');
            }
        } catch (e) {
            GM_log("error in checkForNewPosts: " + e + " - " + initData.gm_keyPostfix);
        }
    });
}

function resetGroupEnvelopeCounts(groupname, gm_keyPostfix) {
    var metaInfo = GM_getObject('selected' + gm_keyPostfix + '.' + groupname);
    if (metaInfo.key_present // function should not be called otherwise
        && !$chk(metaInfo.value)) {
        return;
    }
    var selectedGroup = metaInfo.value;
    selectedGroup.invalidCount = 'true';
    // store as fast as possible
    GM_storeObject('selected' + gm_keyPostfix + '.' + groupname, selectedGroup);
    
    // update the envelope count for the group
    $('NFPN.' + gm_keyPostfix + '.' + groupname).fireEvent('reset-posts-icon');
}

function markThreadAsRead(replyCell, groupname, threadInfo, topic, replies, title, helpforum) {
    var gm_keyPostfix =       helpforum ? preferencesInitData.helpforum.gm_keyPostfix : preferencesInitData.group.gm_keyPostfix;
    var gm_keyPostfixThread = helpforum ? preferencesInitData.forumitem.gm_keyPostfix : preferencesInitData.thread.gm_keyPostfix;
    // uncolorise
    replyCell.style.background = '';
    replyCell.getElements('span').dispose();

    // update thread info within this group
    if ($chk(threadInfo)) {
	threadInfo.replies = replies;
    	threadInfo.newTopic = 'false';
	threadInfo.returnedThread = 'false';
	threadInfo.deletedItems = 'false';
    	threadInfo.newPosts = 0;
    	GM_storeObject(groupname + '.' + threadInfo.topic, threadInfo);
    } else {
        GM_storeObject(groupname + '.' + topic, {
            groupname: groupname,
            topic: topic,
            replies: replies,
            title: title
        });
    }
     
    resetGroupEnvelopeCounts(groupname, gm_keyPostfix);

    // reset the thread (envelope), if also watched
    var reTopicMatch = helpforum ? /.*flickr.com\/help\/forum\/[^\/]*\/(\d+)\// : /.*flickr.com\/groups\/[^\/]*\/discuss\/(\d+)\//;
    var topicId = reTopicMatch.exec(topic)[1];
    var watchedThreadObject = GM_getObject('selected' + gm_keyPostfixThread + '.' + topicId);
    if (watchedThreadObject.key_present) {
        var watchedThread = watchedThreadObject.value;
        if (watchedThread) {
            watchedThread.replies = replies;
            watchedThread.title = title;
            watchedThread.newTopic = 'false';
	    watchedThread.returnedThread = 'false';
	    watchedThread.deletedItems = 'false';
            watchedThread.newPosts = 0;
	    watchedThread.invalidCount = 'true'; // make sure to reread on next invocation
            GM_storeObject('selected' + gm_keyPostfixThread + '.' + topicId, watchedThread);
	    $('NFPN.' + gm_keyPostfixThread + '.' + topicId).fireEvent('reset-posts-icon');
        }
    }
}

function createMarkAsReadEnvelope(data) {
	var column = data.column;
	var newPosts = data.newPosts;
	var groupname = data.groupname;
	var threadInfo = data.threadInfo;
	var topic = data.topic;
	var replies = data.replies;
	var title = data.title;
	var buttonClass = data.buttonClass;
	var helpforum = data.helpforum;

    column.adopt(
        new Element('span', {
	    title: isNaN(newPosts) ? (newPosts === '?' ? 'posts deleted' : 'some new') : newPosts + ' new',
            html:  '(' + (isNaN(newPosts) ? newPosts : newPosts.toLocaleString()) + '&nbsp;new)'
        }).adopt(
            new Element('img', {
                title: "Click to mark this thread as read",
                src: images.mailicon,
		'class': buttonClass,
                styles: {
                    cursor: 'pointer',
                    maxWidth: 12
                },
                events: {
                    click: function (e) {
			markThreadAsRead(this.getParent('td'), groupname, threadInfo, topic, replies, title, helpforum);
                    }
                }
            }))
        );
    column.style.background = '#FFE28A';
    column.style.align = 'right';
}

function processHelpForumPage() {
    processDiscussPage(true);
}

function processDiscussPage(helpforum) {
  try {
    var reGroupnameMatch = /.*flickr.com\/groups\/([^\/]*)\//;
    var gm_keyPostfix = preferencesInitData.group.gm_keyPostfix;

    if (helpforum) {
        reGroupnameMatch = /.*flickr.com\/help\/forum\/([^\/]*)\//;
        gm_keyPostfix = preferencesInitData.helpforum.gm_keyPostfix;
    }
    var groupname = reGroupnameMatch.exec(document.location.href)[1];
    var titleColumn = getMain().getElement('table.TopicListing').getElement('tr').getElement('th,td').get('html').match(/UCP-ng/) ? 1 : 0;
    var metaInfo = GM_getObject('selected' + gm_keyPostfix + '.' + groupname);
    if (metaInfo.key_present) {
        var storedInfo = metaInfo.value;
        if ($chk(storedInfo)) {
            // sync number of unread posts on group's envelope
            storedInfo.newPosts = 0;
            // read all rows
            // for every row, compare with the stored value
            // remove threads from cache that are no longer on the page
            var storedThreads = retrieveThreadsFromCache(groupname);
            var newlyAddedGroup = (storedThreads.getKeys().length === 0); 
            try {
                getMain().getElements('table.TopicListing').each(function (table, tableIdx) { // helpforum has two tables
                  table.getElements('tr').each( function (row, rowIdx) {
		    var columns = row.getElements('th,td'); // Discuss page uses th, forum uses td
                    if (rowIdx === 0) { // header
                        // add 'mark all read' icon
                        var repliesHeader = columns[titleColumn + 2];
                        repliesHeader.adopt(
                            new Element('img', {
                                title: "Click to mark all threads as read",
                                src: images.mailicon,
                                styles: {
                                    cursor: 'pointer',
                                    maxWidth: 12
                                },
                                events: {
                                    click: function (evt) {
				    	$$('img.NFPN-mark-as-read-button-' + tableIdx).each(function(button) {
						button.fireEvent('click');
					});
                                        resetGroupEnvelopeCounts(groupname, 
                                                                gm_keyPostfix);
                                    }
                                }
                            })
                        );
                        return;
                    } // end header
                    //$$('span.New').dispose();
                    var title = columns[titleColumn].getElement('a').textContent;
                    var topic = columns[titleColumn].getElement('a').href;
                    var replies = parseInt(columns[titleColumn + 2].textContent.replace(/(\.|,)/g, ''), 10);
		    
                    if (newlyAddedGroup) {
                        GM_storeObject(groupname + '.' + topic, {
                            groupname: groupname,
                            topic: topic,
                            replies: replies,
                            title: title
                        });
                        return;
                    }
                    var threadInfo = storedThreads.get(topic);
                    if ($chk(threadInfo)) {
		    	if (threadInfo.returnedThread === 'true' || threadInfo.deletedItems === 'true') {
				// new post(s): colorize
				storedInfo.newPosts += 1;
				storedInfo.returnedThreads = 'true';
				createMarkAsReadEnvelope({
					column: columns[titleColumn + 2], 
					newPosts: threadInfo.deletedItems === 'true' ? '?' : 'x',
					groupname: groupname,
					threadInfo: threadInfo,
					topic: topic,
					replies: replies,
					title: title,
					buttonClass: 'NFPN-mark-as-read-button-' + tableIdx,
					helpforum: helpforum });

			} else if (threadInfo.replies < replies || threadInfo.newTopic === 'true') {
                            // new posts: colorize
                            var newPosts = (threadInfo.newTopic === 'true' ? replies + 1 : replies - threadInfo.replies);
                            threadInfo.newPosts = newPosts;
            		    storedInfo.newPosts += newPosts;
                            GM_storeObject(groupname + '.' + threadInfo.topic, threadInfo);
			    createMarkAsReadEnvelope({
			    		column: columns[titleColumn + 2],
					newPosts: newPosts,
					groupname: groupname,
					threadInfo: threadInfo,
					topic: topic,
					replies: replies,
					title: title,
					buttonClass: 'NFPN-mark-as-read-button-' + tableIdx,
					helpforum: helpforum });
                
                        } else if (threadInfo.replies > replies) { // someone deleted a comment; record
			    // treat it as a returning thread
			    storedInfo.returnedThreads = 'true';
			    storedInfo.newPosts += 1;
			    threadInfo.replies = replies;
			    threadInfo.newPosts = 0;
			    threadInfo.deletedItems = 'true';
                            GM_storeObject(groupname + '.' + threadInfo.topic, threadInfo);
			    createMarkAsReadEnvelope({
			    		column: columns[titleColumn + 2],
					newPosts: '?',
					groupname: groupname,
					threadInfo: threadInfo,
					topic: topic,
					replies: replies,
					title: title,
					buttonClass: 'NFPN-mark-as-read-button-' + tableIdx,
					helpforum: helpforum });
			}
    
                        storedThreads.erase(topic);

                    } else {
                        if (replies === 0) {
                            // new topic: colorize
			    createMarkAsReadEnvelope({
			    		column: columns[titleColumn + 2],
					newPosts: 1,
					groupname: groupname,
					topic: topic,
					replies: 0,
					title: title,
					buttonClass: 'NFPN-mark-as-read-button-' + tableIdx,
					helpforum: helpforum });
                            // and store
                            GM_storeObject(groupname + '.' + topic, {
                                groupname: groupname,
                                topic: topic,
                                replies: 0,
                                newPosts: 1,
                                title: title,
                                newTopic: 'true'
                            });
            		    storedInfo.newPosts += 1;
                        } else {
                            // could be a thread returning from pageX
                            createMarkAsReadEnvelope({
			    		column: columns[titleColumn + 2],
					newPosts: 'x',
					groupname: groupname,
					threadInfo: undefined,
					topic: topic,
					replies: replies,
					title: title,
					buttonClass: 'NFPN-mark-as-read-button-' + tableIdx,
					helpforum: helpforum });
                            // in any case, don't store! => if not stored, this thread will not count as an extra 1+ on other pages!
            		    storedInfo.newPosts += 1;
			    storedInfo.returnedThreads = 'true';
			    GM_storeObject(groupname + '.' + topic, {
			    	groupname: groupname,
				topic: topic,
				replies: replies,
				newPosts: 0,
				title: title,
				returnedThread: 'true'
                            });
                        }
		    }
                  });
                });
                // those that are still in the hash, but have not been recognized, have fallen of the page
                storedThreads.getKeys().each(function (key) {
                    GM_deleteValue(groupname + '.' + key);
                });
                // older versions didn't store the group id, needed in the request for RSS feed
                if ((!$chk(storedInfo.groupId) && !$chk(storedInfo.privateGroup))|| !$chk(storedInfo.title)) {
                    var groupId = GM_getGroupId();
		    var title = helpforum ?
                            	$('Tertiary').textContent.replace(/\n[^]*$/im, '') :
		    		$('cattington_outer').getElement('h1 a').get('html');
                    storedInfo.groupId = groupId;
		    storedInfo.privateGroup = !$chk(groupId);
		    storedInfo.title = title;
                }
            } catch (e) {
                GM_log("error processing Discusspage: " + e);
            }
            GM_storeObject('selected' + gm_keyPostfix + '.' + groupname, storedInfo);
	    $('NFPN.' + gm_keyPostfix + '.' + groupname).fireEvent('notify-new-posts', { 
	    	newPosts: storedInfo.newPosts,
		returnedThreads: storedInfo.returnedThreads});
        }
        if (helpforum) {
            watchTypes.helpforum.id = groupname;
            addMinusIcon(watchTypes.helpforum);
        } else {
            watchTypes.group.id = groupname;
            addMinusIcon(watchTypes.group);
        }
    } else {
        if (helpforum) {
            watchTypes.helpforum.id = groupname;
            addPlusIcon(watchTypes.helpforum);
        } else {
            watchTypes.group.id = groupname;
            addPlusIcon(watchTypes.group);
        }
    }
    getMain().getElements('table.TopicListing').each(function (table) { // helpforum has two tables
        table.getElements('tr').each( function (row, index) {
            if (index === 0) { // header
	    	return;
	    }
            var columns = row.getElements('td');
            var title = columns[titleColumn].getElement('a').textContent;
            var topic = columns[titleColumn].getElement('a').href;
            var replies = parseInt(columns[titleColumn + 2].textContent.replace(/(\.|,)/g, ''), 10);
            var topicId = helpforum ? topic.match(/.*flickr.com\/help\/forum\/[^\/]+\/(\d+)/)[1]
                                    : topic.match(/.*flickr.com\/groups\/[^\/]+\/discuss\/(\d+)/)[1];
	    // add +/- icon for each thread
	    addPlusMinIconOnDiscussPage({
		topic: topicId,
		groupname: groupname,
		helpforum: helpforum,
		cell: columns[titleColumn + 2]
	    });
	});
    });
  } catch (e) {
      GM_log("unable to read groupname: " + e);
  }
}

function countPostsInThreadPage(element) {
    if (!$chk(element)) {
        element = document;
    }

    var replies = 0;
    var paginator = element.getElement('div.Paginator');
    if (!$chk(paginator) || paginator.length === 0) {
        // count the replies
	if (element.getElements('td.Said').length == 0) {
		return { result: false, msg: "no posts found" };
	}
        replies = element.getElements('td.Said').length - 1; // not the challenge announcement
    } else {
        var results = element.getElement('div.Results');
        replies = results.get('html').match(/\([^\d]*\d+[^\d]+\d+[^\d]+([\d\.,]+)/)[1];
                                                         //    (2001 to 2045 of 2,500
        replies = replies.replace(/(\.|,)/g, '');
        replies = parseInt(replies, 10);
    }
    return { result: true, items: replies };
}

function lastPageInDiscussionThread(helpforum) {
    if (document.location.href.test(/lastpage/)) {
    	return true;
    }
    // if there is only 1 page, it's the last :-)
    var paginators = $$('div.Paginator');
    if (paginators.length === 0) {
        return true;
    }
    var paginator = paginators[0];
    if ($chk(paginator.getElement('span.AtEnd'))) {
        return true;
    }
    return false;
}

function colorizeNewPostsInThreadPage(newPosts, helpforum) {
    if (newPosts && newPosts > 0) {
        // colorize the new posts (if on the last page)
        if ( (preferences.colorizeNewPosts() || preferences.scrollToNewPosts()) && lastPageInDiscussionThread(helpforum)) {
            var replyElements = $('DiscussTopic').getElement('table.TopicReply').getElements('td.Who');
            var oldestUnreadPost;
            // colorize the unread ones
            replyElements.each(function (replyElement, idx) {
                if (idx < replyElements.length - newPosts) {
                    return;
                }
                oldestUnreadPost = oldestUnreadPost || replyElement;
                if (preferences.colorizeNewPosts()) replyElement.setStyle('background', '#FFE28A');
            });
            if (replyElements.length === newPosts - 1 && $$('div.Paginator').length === 0) { // also color the topic entry
                oldestUnreadPost = undefined; // reset
                if (preferences.colorizeNewPosts()) $('DiscussTopic').getElement('td.Who').setStyle('background', '#FFE28A');
            }
            if (replyElements.length < newPosts && $$('div.Paginator').length > 0) { // also color the 'prev' button
                oldestUnreadPost = undefined; // reset
                if (preferences.colorizeNewPosts()) $$('a.Prev').setStyle('background', '#FFE28A');
            }
            if ($chk(oldestUnreadPost) && preferences.scrollToNewPosts()) {
                try {
                    var position = oldestUnreadPost.getParent('tr').getOffsets();
		    if (position.y > 10) {
                    	$(window).scrollTo(0, position.y);
		    }
                } catch(e) {
                    GM_log("getTop() error: " + e);
                }
            }
        }
    }
}

function processThreadPage(helpforum) {
  try {
    var reGroupnameMatch = /.*flickr.com\/groups\/([^\/]*)\//;
    var reTopicMatch = /.*flickr.com\/groups\/[^\/]*\/discuss\/(\d+)(?:\/)?/;
    var gm_keyPostfix = 'Group';
    var gm_keyPostfixThread = 'Thread';
    if (helpforum) {
        reGroupnameMatch = /.*flickr.com\/help\/forum\/([^\/]*)/;
        reTopicMatch = /.*flickr.com\/help\/forum\/[^\/]*\/(\d+)/;
        gm_keyPostfix = 'Helpforum';
        gm_keyPostfixThread = 'Forumitem';
    }
    var groupname = reGroupnameMatch.exec(document.location.href)[1];
    var topic = reTopicMatch.exec(document.location.href)[1];
    var threadMetaInfo = GM_getObject('selected' + gm_keyPostfixThread + '.' + topic);
    var groupMetaInfo = GM_getObject('selected' + gm_keyPostfix + '.' + groupname);
    var title;
	    
    if (lastPageInDiscussionThread(helpforum)) { // reset numbers, only when reading the last page of the thread

	// thread, or group, is watched - update replies
    	if ($chk(threadMetaInfo.key_present) || $chk(groupMetaInfo.key_present)) {
        	if (document.location.href.match(/edit(?:\/)?$/)) {
            		title = $('Tertiary').getElements('a').getLast().get('html');
            		if (title === 'Discuss' && $chk($('GoodStuff').getElement('input[type=text]'))) {
                		// editing the topic announcement, does not show the title!
                		return;
            		}
        	} else {
            		var count = countPostsInThreadPage(document);
			if (count.result == false) {
				$('NFPN.' + gm_keyPostfix + '.' + topic).fireEvent('notify-error',count.msg);
				return;
			}
            		title = $('GoodStuff').getElement('h2').get('html');
        	}
    	}
	var newPostsOnGroupWatch = 0;
	var newPostsOnThreadWatch = 0;
	// group is watched
    	if (groupMetaInfo.key_present) {
        	var storedGroupInfo = groupMetaInfo.value;
        	if ($chk(storedGroupInfo)) {
            		var url = helpforum ?
                		document.location.href.match(/.*flickr.com\/help\/forum\/[^\/]+\/\d+\//)[0] : // not the 'page3' part, or '#reply', or ...
                		document.location.href.match(/.*flickr.com\/groups\/[^\/]+\/discuss\/\d+(?:\/)?/)[0]; // not the 'page3' part, or '#reply', or ...
            		var metaInfo = GM_getObject(groupname + '.' + url);
			// we found thread info within the group's data
            		if (metaInfo.key_present) {
                		var threadInfo = metaInfo.value;
				if ($chk(threadInfo)) {
	                		if (threadInfo.returnedThread !== 'true' && threadInfo.deletedItems !== 'true') {
						if ($chk(count) && count.items > threadInfo.replies) {
							threadInfo.newPosts = count.items - threadInfo.replies;
							newPostsOnGroupWatch = threadInfo.newPosts;
						}
                			}
					// decrement groupinfo.newposts
					if (threadInfo.returnedThread === 'true' || threadInfo.deletedItems === 'true') {
						storedGroupInfo.newPosts = storedGroupInfo.newPosts - 1;
					} else if (threadInfo.newTopic === 'true') {
						storedGroupInfo.newPosts = storedGroupInfo.newPosts - threadInfo.replies - 1;
					} else {
						storedGroupInfo.newPosts = storedGroupInfo.newPosts - threadInfo.newPosts;
					}
				}
                	}
			// overwrite thread-within-group info: 
	        	GM_storeObject(groupname + '.' + url, { 
        	        	groupname: groupname, 
                		topic: url, 
	                	replies: count.items, 
				newPosts: 0,
        	        	title: title
	            	});
	    		storedGroupInfo.invalidCount = 'true'; // recount on next invocation
            		if (!$chk(storedGroupInfo.groupId)) {
                		storedGroupInfo.groupId = GM_getGroupId();
            		}
            		GM_storeObject('selected' + gm_keyPostfix + '.' + groupname, storedGroupInfo);
	    		// update the envelope count
			if (storedGroupInfo.newPosts > 0) {
				$('NFPN.' + gm_keyPostfix + '.' + groupname).fireEvent('notify-new-posts', {
					newPosts: storedGroupInfo.newPosts,
					returnedThreads: storedGroupInfo.returnedThreads});
			} else {
				$('NFPN.' + gm_keyPostfix + '.' + groupname).fireEvent('reset-posts-icon');
			}
        	}
    	} // group is watched

    	if (threadMetaInfo.key_present) {
        	var storedThreadInfo = threadMetaInfo.value;
        	if ($chk(storedThreadInfo)) {
            		// backporting data from older versions
            		if (!$chk(storedThreadInfo.groupId)) {
                		storedThreadInfo.groupId = GM_getGroupId();
            		}
			newPostsOnThreadWatch = storedThreadInfo.newPosts;
        		storedThreadInfo.replies = count.items;
	        	storedThreadInfo.newPosts = 0;
        		storedThreadInfo.title = title;

	        	GM_storeObject('selected' + gm_keyPostfixThread + '.' + topic, storedThreadInfo);
			$('NFPN.' + gm_keyPostfixThread + '.' + storedThreadInfo.topic).fireEvent('reset-posts-icon');
	    		//if (storedThreadInfo.locked) markThreadLocked(gm_keyPostfixThread, storedThreadInfo.topic);
        	}
	}
	if (newPostsOnGroupWatch > 0 || newPostsOnThreadWatch > 0) {
		colorizeNewPostsInThreadPage(Math.max(newPostsOnGroupWatch,newPostsOnThreadWatch), helpforum);
	}
    } else {
	if (threadMetaInfo.key_present) {
	    if (threadMetaInfo.value.newPosts > 0) {
		$('NFPN.' + gm_keyPostfixThread + '.' + threadMetaInfo.value.topic).fireEvent('notify-new-posts', { newPosts: threadMetaInfo.value.newPosts });
	    } else {
	    	$('NFPN.' + gm_keyPostfixThread + '.' + threadMetaInfo.value.topic).fireEvent('reset-posts-icon');
	    }
	}
    } // last page
    
    if (threadMetaInfo.key_present) {
        if (helpforum) {
            watchTypes.forumitem.id = topic;
            watchTypes.forumitem.groupname = groupname;
            addMinusIcon(watchTypes.forumitem);
        } else {
            watchTypes.thread.id = topic;
            watchTypes.thread.groupname = groupname;
            addMinusIcon(watchTypes.thread);
        }
    } else {
        if (helpforum) {
            watchTypes.forumitem.id = topic;
            watchTypes.forumitem.groupname = groupname;
            addPlusIcon(watchTypes.forumitem);
        } else {
            watchTypes.thread.id = topic;
            watchTypes.thread.groupname = groupname;
            addPlusIcon(watchTypes.thread);
        }
    }
            		
	// group is watched
    	if (groupMetaInfo.key_present) {
			// show number of new posts in the left column
            		$('Hint').getElements('table.TopicListing tr td').each(function(threadCell) {
                		var topicUrlAnchor = threadCell.getElement('a');
                		if (!topicUrlAnchor) {
                    			return;
                		}
                		var topicUrlMatch = topicUrlAnchor.href.match(/.*flickr.com\/groups\/[^\/]+\/discuss\/\d+\//); // drop pageX
                		if (!topicUrlMatch && helpforum) {
                    			topicUrlMatch = topicUrlAnchor.href.match(/.*flickr.com\/help\/forum\/[^\/]+\/\d+\//);
                		}
                		if (!topicUrlMatch) {
                    			return;
                		}
                		var topicUrl = topicUrlMatch[0];
                		var metaInfo = GM_getObject(groupname + '.' + topicUrl);
                		if (metaInfo.key_present) {
                    			var threadInfo = metaInfo.value;
                    			if (threadInfo) {
						if (threadInfo.returnedThread === 'true') {
							topicUrlAnchor.adopt(new Element('span', {
								html: ' (x&nbsp;new)'
							}));
						} else if (threadInfo.deletedItems === 'true') {
							topicUrlAnchor.adopt(new Element('span', {
								html: ' (?&nbsp;new)'
							}));
                        			} else if (threadInfo.newTopic === 'true') {
                            				topicUrlAnchor.adopt(new Element('span', {
                                				html: ' (' + (threadInfo.replies + 1).toLocaleString() + '&nbsp;new)'
                            				}));
                        			} else if (threadInfo.newPosts && threadInfo.newPosts > 0) {
                            				topicUrlAnchor.adopt(new Element('span', {
                                				html: ' (' + threadInfo.newPosts.toLocaleString() + '&nbsp;new)'
                            				}));
                        			}
                    			}
                		} else {
                    			topicUrlAnchor.adopt(new Element('span', {
                        			html: ' (x&nbsp;new)'
                    			}));
                		}
            		});
	}
  } catch (e) { // hiccups ??
    GM_log("error processing thread page: " + e);
  }
}

function processForumItemPage() {
    processThreadPage(true);
}

function lastPageInPhotoPage() {
    var lastpage = false;
    // if there is only 1 page, it's the last :-)
    var paginator = $$('div.Paginator');
    if (paginator.length === 0) {
        lastpage = true;
    } else {
        if ($chk(paginator.getElement('span.AtEnd'))) {
            lastpage = true;
        }
    }
    return lastpage;
}

function colorizeNewPostsInPhotoPage(newPosts) {
    if (newPosts && newPosts > 0) {
        // colorize the new posts (if on the last page)
        if ((preferences.colorizeNewPosts() || preferences.scrollToNewPosts()) && lastPageInPhotoPage()) {
            var replyElements = $('photo-activity').getElements('li.comment-block');
            var oldestNewComment;
            // colorize the unread ones
            replyElements.each(function (replyElement, idx) {
                if (idx < replyElements.length - newPosts) {
                    return;
                }
                oldestNewComment = oldestNewComment || replyElement;
                if (preferences.colorizeNewPosts()) {
                    replyElement.setStyle('background', '#FFE28A');
                    replyElement.getElement('div.comment-content').setStyle('background', 'white');
                }
            });
            if (replyElements.length < newPosts && $$('div.Paginator').length > 0) { // also color the 'prev' button
                oldestNewComment = undefined; // reset
                if (preferences.colorizeNewPosts()) $$('a.Prev').setStyle('background', '#FFE28A');
            }
            if ($chk(oldestNewComment) && preferences.scrollToNewPosts()) {
                try {
                    $(window).scrollTo(0, oldestNewComment.getTop());
                } catch (e) {
                    GM_log("getTop() error: " + e); // TODO: find out what's wrong
                }
            }
        }
    }
}

function countCommentsInPhotoPage() {
    var comments = 0;
    var paginator = $$('div.Paginator');
    if ($chk(paginator) && paginator.length > 0) {
        var results = $$('div.Results')[0];
        comments = results.get('html').match(/\(([\d\.,]+) [^\d]+\)/)[1];
        comments = parseInt(comments.replace(/(\.|,)/g, ''), 10);
    } else {
        // count the replies
        comments = $$('.comment-block').length;
    }
    return { result: true, items: comments };
}

function processSomethingPage(data) {
	var debug = data.debug;
	var initData = preferencesInitData[data.type];
	var watchType = watchTypes[data.type];
	var id = data.id;
	var colorizeFunction = $chk(data.colorizeFunction) ? data.colorizeFunction : function () {};
	var countItemsFunction = $chk(data.countItemsFunction) ? data.countItemsFunction : function () { return { result: true, items: 0 }; };
	var lastPageFunction = $chk(data.lastPageFunction) ? data.lastPageFunction : function () { return true; };
	var updateInfo = $chk(data.updateInfo) ? data.updateInfo : function () {};
	var updateIcon = $chk(data.updateIcon) ? data.updateIcon : function () {};

	if (debug) GM_log("DEBUG: reading 'selected" + initData.gm_keyPostfix + '.' + id); 
	var metaInfo = GM_getObject('selected' + initData.gm_keyPostfix + '.' + id);
	if (metaInfo.key_present) {
        	var storedInfo = metaInfo.value;
        	if ($chk(storedInfo)) {
			if (debug) GM_log("DEBUG: using stored info");
    		    if (lastPageFunction()) { // reset numbers, only when reading the last page of the thread
		    	if (debug) GM_log("DEBUG: on last page");
            		var count = countItemsFunction();
			if (count.result == false) {
				$('NFPN.' + initData.gm_keyPostfix + '.' + id).fireEvent('notify-error',count.msg);
			} else {
            			colorizeFunction(Math.max(storedInfo[initData.newitems], count.items - storedInfo[initData.items]));
				if (debug) GM_log(["DEBUG: storing",
            					'selected' + initData.gm_keyPostfix + '.' + id,
            					initData.items + ':' + count.items,
            					initData.newitems + ':' + 0].join('\n'));
            			storedInfo[initData.items] = count.items;
            			storedInfo[initData.newitems] = 0;
				updateInfo(storedInfo);
            			GM_storeObject('selected' + initData.gm_keyPostfix + '.' + id, storedInfo);
	    			$('NFPN.' + initData.gm_keyPostfix + '.' + id).fireEvent('reset-posts-icon');
				updateIcon(storedInfo);
			}
		    } else {
		    	if (storedInfo[initData.newitems] > 0) {
				$('NFPN.' + initData.gm_keyPostfix + '.' + id).fireEvent('notify-new-posts', { newPosts: storedInfo[initData.newItems] });
			} else {
				$('NFPN.' + initData.gm_keyPostfix + '.' + id).fireEvent('reset-posts-icon');
			}
		    }
    		} else {
			$('NFPN.' + initData.gm_keyPostfix + '.' + id).fireEvent('notify-error', "key present,but no data");
		}
            	addMinusIcon(watchType);
    	} else {
       	    addPlusIcon(watchType);
    	}
}

function processPhotoPage() {
    var rePhotoMatch = /.*flickr.com\/photos\/([^\/]*)\/(\d+)/;
    var username = rePhotoMatch.exec(document.location.href)[1];
    var photoId = rePhotoMatch.exec(document.location.href)[2];

    watchTypes['photo'].id = photoId;
    watchTypes['photo'].username = username;

    processSomethingPage({
    	type: 'photo',
	id: photoId,
	colorizeFunction: function(newComments) {
            colorizeNewPostsInPhotoPage(newComments);
	},
	countItemsFunction: function() {
            return countCommentsInPhotoPage();
	},
	lastPageFunction: function () {
		return lastPageInPhotoPage();
	},
	updateInfo: function(storedInfo) {
            	var main = getMain();
            	var title = main.getElement('h1').get('html');
            	storedInfo.title = title;
	}
    });
}

function countPhotosInPhotostream() {
    var photos = 0;
    var paginator = $$('div.Paginator');
    if (!$chk(paginator) || paginator.length === 0) {
        // count the replies
        photos = $$('div.StreamView').length;
    } else {
        var results = $$('div.Results')[0];
        photos = results.get('html').match(/\(([\d\.,]+) [^\d]+\)/)[1];
        photos = parseInt(photos.replace(/(\.|,)/g, ''), 10);
    }
    return { result: true, items: photos };
}

function processPhotostreamPage() {
  try {
    var nsid = GM_getPhotostreamOwnerNsid();
    if (!$chk(nsid)) { // no photos yet
        var photostreamUrl = $('SubNav').getElement('td.Buddy a').get('href');
        var reNsidMatch = /.*\/(\d+@\w\d{2})/;
	if (photostreamUrl.match(reNsidMatch)) {
        	nsid = reNsidMatch.exec(photostreamUrl)[1];
	} else {
		var buddyIcon = $('SubNav').getElement('td.Buddy a img');
		if ($chk(buddyIcon)) {
			var buddyIconUrl = buddyIcon.get('src');
			nsid = reNsidMatch.exec(buddyIconUrl)[1];
		}
	}
    }
    var rePhotostreamMatch = /.*flickr.com\/photos\/([^\/]*)\//;
    var username = rePhotostreamMatch.exec(document.location.href)[1];
    
    watchTypes.photostream.id = nsid;
    watchTypes.photostream.username = username;

    processSomethingPage({
    	type: 'photostream',
	id: nsid,
	countItemsFunction: function() {
            return countPhotosInPhotostream();
	},
	updateInfo: function(storedInfo) {
            storedInfo.username = username;
	}
    });

  } catch (e) {
	GM_log("error processing photostream page: " + e);
  }
}

function countMembersInGroup() {
    var members = 0;
    $$('h2').get('html').each(function (count) { // 'count' contains 'admins(n)'
        try {
            var num = /\w+\s*\(\s*([\d\.,]+)\s*\)/.exec(count)[1];
            members += parseInt(num.replace(/(\.|,)/, ''), 10);
        } catch (e) {
            return { result: false, msg: "error parsing count on members page: " + e };
        }
    });
    return { result: true, items: members };
}

function processGroupMembersPage() {
    var reGroupmembersMatch = /.*flickr.com\/groups_members.gne.*id=(\d+@\w\d{2})/;
    if (reGroupmembersMatch.test(document.location.href)) {
        var groupId = reGroupmembersMatch.exec(document.location.href)[1];
    } else {
        var href = getMain().getElement('a[href*=groups_members_detail.gne]').href;
        reGroupmembersMatch = /groups_members_detail.gne.*id=(\d+\@\w\d{2})/;
        groupId = reGroupmembersMatch.exec(href)[1];
    }
    var groupname = $('cattington_outer').getElement('h1 a').get('html');

    watchTypes.groupmembers.id = groupId;
    watchTypes.groupmembers.title = groupname;
    
    processSomethingPage({
    	type: 'groupmembers',
	id: groupId,
	countItemsFunction: function() {
            return countMembersInGroup();
	},
	updateInfo: function(storedInfo) {
            storedInfo.title = groupname;
	}
    });
}

function countReversedContacts(element) {
    var members = 0;
    if (!$chk(element)) {
        element = $(document);
    }
    var paginator = element.getElement('div.Paginator');
    if (!$chk(paginator) || paginator.length === 0) {
        // count the members
        members = element.getElements('td.contact-list-name').length;
    } else {
        var results = element.getElement('div.Results');
        members = results.get('html').match(/\(([\d\.,]+) [^\d]+\)/)[1];
        members = parseInt(members.replace(/(\.|,)/g, ''), 10);
    }
    return { result: true, items: members };
}

function processReversedContactsPage() {
    var userNsid = GM_getUserNsid();
    var title = 'Your reverse contacts';

    watchTypes.reversedcontacts.id = userNsid;
    watchTypes.reversedcontacts.title = title;

    processSomethingPage({
    	type: 'reversedcontacts',
	id: userNsid,
	countItemsFunction: function() {
            return countReversedContacts();
	},
	updateInfo: function(storedInfo) {
            storedInfo.title = title;
	}
    });
}

function countPendingItems(element) {
    var items = 0;
    if (!$chk(element)) {
        element = $(document);
    }
    var paginator = element.getElement('div.Paginator');
    if ($chk(paginator) && paginator.length > 0) {
        var results = element.getElement('div.Results');
        items = results.get('html').match(/\(([\d\.,]+) [^\d]+\)/)[1];
        items = parseInt(items.replace(/(\.|,)/g, ''), 10);
    } else {
        // count the items
        items = element.getElements('td.gPendPic').length;
    }
    return { result: true, items: items };
}

function countPendingTestimonials(element) {
    var items = 0;
    if (!$chk(element)) {
        element = $(document);
    }
    return { result: true, items: element.getElements('input[name*=approve]').length };
}

function processPendingItemsPage() {
    var reGroupnameMatch = /.*flickr.com\/groups\/([^\/]*)\//;
    var groupname = reGroupnameMatch.exec(document.location.href)[1];
    var title = $('cattington_outer').getElement('h1 a').get('html');

    watchTypes.pendingitems.id = groupname;
    watchTypes.pendingitems.title = title;

    processSomethingPage({
    	type: 'pendingitems',
	id: groupname,
	countItemsFunction: function() {
            return countPendingItems();
	},
	updateInfo: function(storedInfo) {
            storedInfo.title = title;
	}
    });
}
function processPendingTestimonialsPage() {
    var userNsid = GM_getUserNsid();
    var realname = $('head-status').getElement('a').textContent;

    watchTypes.pendingtestimonials.id = userNsid;
    watchTypes.pendingtestimonials.title = realname;

    processSomethingPage({
    	type: 'pendingtestimonials',
	id: userNsid,
	countItemsFunction: function() {
            return countPendingTestimonials();
	},
	updateInfo: function(storedInfo) {
            storedInfo.title = realname;
	}
    });
}

function lastPageInAppPage() {
    var lastpage = false;
    // if there is only 1 page, it's the last :-)
    var paginator = $$('div.Paginator');
    if (paginator.length === 0) {
        lastpage = true;
    } else {
        if ($chk(paginator.getElement('span.AtEnd'))) {
            lastpage = true;
        }
    }
    return lastpage;
}

function colorizeNewPostsInAppPage(newPosts) {
    if (newPosts && newPosts > 0) {
        // colorize the new posts (if on the last page)
        if (preferences.colorizeNewPosts() && lastPageInAppPage()) {
            var replyElements = $('DiscussPhoto').getElements('div.comment-owner');
            // colorize the unread ones
            replyElements.each(function (replyElement, idx) {
                if (idx < replyElements.length - newPosts) {
                    return;
                }
                replyElement.setStyle('background', '#FFE28A');
            });
            if (replyElements.length < newPosts && $$('div.Paginator').length > 0) { // also color the 'prev' button
                $$('a.Prev').setStyle('background', '#FFE28A');
            }
        }
    }
}

function countCommentsInAppPage(element) {
    if (!$chk(element)) {
        element = $(document);
    }
    var comments = 0;
    var paginator = element.getElements('div.Paginator');
    if ($chk(paginator) && paginator.length > 0) {
        var results = element.getElement('div.Results');
        comments = results.get('html').match(/\(([\d\.,]+) [^\d]+\)/)[1];
        comments = parseInt(comments.replace(/(\.|,)/g, ''), 10);
    } else {
        // count the replies
        comments = element.getElements('.comment-block').length;
    }
    return { result: true, items: comments };
}

function processAppPage() {
// TODO: remove trailing \/
// some folks provide links within Flickr without the trailing /
    var reAppidMatch = /.*flickr.com\/services\/apps\/(\d+)\//;
    var appId = reAppidMatch.exec(document.location.href)[1];
    var title = getMain().getElement('h1').get('html');
    var username = $('ag-owner-attr').getElement('b').get('html');

    watchTypes.application.id = appId;
    watchTypes.application.username = username;
    watchTypes.application.title = title;

    processSomethingPage({
    	type: 'application',
	id: appId,
	colorizeFunction: function(newComments) {
            colorizeNewPostsInAppPage(newComments);
	},
	countItemsFunction: function() {
            return countCommentsInAppPage();
	},
	lastPageFunction: function() {
	    return lastPageInAppPage();
	},
	updateInfo: function(storedInfo) {
            storedInfo.username = username;
            storedInfo.title = title;
	}
    });
}

function lastPageInSetPage() {
    // TODO: check for a set with multiple page comments
    return lastPageInAppPage();
}

function constructSetUrl(setId) {
//GM_log("DEBUG: running sync request");
    var retval = undefined;
    new Request({
        url: 'http://www.flickr.com',
        async: false,
        onSuccess: function (responseText, responseXML) {
            var result = JSON.parse(responseText);
            if (result.stat === 'fail') {
                GM_log("error reading user information: " + result.message);
                return;
            }
            var ownerid = result.photoset.owner;
            var username = resolveUsername(ownerid).username;
            retval = 'http://www.flickr.com/photos/' + username + '/sets/' + setId + '/comments/';
        },
        onFailure: function (response) {
            GM_log("error constructing set url " + response.statusText);
        }
    }).get('/services/rest', {
        api_key: GM_getMagisterLudi(),
        auth_hash: GM_getAuthHash(),
        auth_token: GM_getAuthToken(),
        format: 'json',
        method: 'flickr.photosets.getInfo',
        nojsoncallback: 1,
        photoset_id: setId
    });
    return retval;
}

function colorizeNewPostsInSetPage(newPosts) {
    if (newPosts && newPosts > 0) {
        // colorize the new posts (if on the last page)
        if (preferences.colorizeNewPosts() && lastPageInSetPage()) {
            var replyElements = $('ViewSet').getElement('table.SetComments').getElements('td.Comment');
            // colorize the unread ones
            replyElements.each(function (replyElement, idx) {
                if (idx < replyElements.length - newPosts) {
                    return;
                }
                replyElement.getParent('tr').getElement('td').setStyle('background', '#FFE28A');
            });
            if (replyElements.length < newPosts && $$('div.Paginator').length > 0) { // also color the 'prev' button
                $$('a.Prev').setStyle('background', '#FFE28A');
            }
        }
    }
}

function countCommentsInSetPage(element) { // TODO: check for multipage comments
    if (!$chk(element)) {
        element = $(document);
    }
    var comments = 0;
    var paginator = element.getElements('div.Paginator');
    if ($chk(paginator) && paginator.length > 0) {
        var results = element.getElement('div.Results');
        comments = results.get('html').match(/\(([\d\.,]+) [^\d]+\)/)[1];
        comments = parseInt(comments.replace(/(\.|,)/g, ''), 10);
    } else {
        // count the replies
        comments = element.getElements('td.Comment').length;
    }
    return { result: true, items: comments };
}

function processSetPage() {
    var reSetidMatch = /.*flickr.com\/photos\/([^\/]+)\/sets\/(\d+)\/comments/;
    var matches = reSetidMatch.exec(document.location.href);
    var username = matches[1];
    var setId = matches[2];
    var title = getMain().getElement('h1').get('html').replace(/^\s*(.*)\s*$/, "$1"); // remove leading and trailing white-space
    var realname = getMain().getElement('a').get('html');
    
    watchTypes.set.id = setId;
    watchTypes.set.username = username;
    watchTypes.set.title = title;

    processSomethingPage({
    	type: 'set',
	id: setId,
	colorizeFunction: function(newComments) {
            colorizeNewPostsInSetPage(newComments);
	},
	countItemsFunction: function() {
            return countCommentsInSetPage();
	},
	lastPageFunction: function() {
	    return lastPageInSetPage();
	},
	updateInfo: function(storedInfo) {
            storedInfo.username = username;
            storedInfo.realname = realname;
            storedInfo.title = title;
	}
    });
}

function countPhotosInGroup() {
    var photos = 0;
    var paginator = $$('div.Paginator');
    if (!$chk(paginator) || paginator.length === 0) {
        // count the photos
	try {
        	photos = getMain().getElement('.HoldPhotos').getElements('.PoolList').length;
	} catch (e) {
		return { result: false, msg: "no photos found" };
	}
    } else {
        var results = $$('div.Results')[0];
        photos = results.get('html').match(/\(([\d\.,]+) [^\d]+\)/)[1];
        photos = parseInt(photos.replace(/(\.|,)/g, ''), 10);
    }
    return { result: true, items: photos };
}

function processGroupPoolPage() {
    var title = $('cattington_outer').getElement('h1 a').get('html');
    var groupId = GM_getGroupId();

    watchTypes.grouppool.id = groupId;
    watchTypes.grouppool.title = title;

    processSomethingPage({
    	type: 'grouppool',
	id: groupId,
	countItemsFunction: function() {
            return countPhotosInGroup();
	},
	updateInfo: function(storedInfo) {
            storedInfo.title = title;
	}
    });
}

if (window.name === 'Log page') {
    return; //don't process log page
}

preferences = new NFPNPreferences();

// first call checkForNewPosts; it triggers the creation of the envelopes
if (document.location.href.match('http://www.flickr.com')) {
    checkForNewPosts();
}

if (document.location.href.match(/www.flickr.com\/groups\/[^\/]+\/discuss\/\d+/)) {
   if (!document.location.href.match(/www.flickr.com\/groups\/[^\/]+\/discuss\/\d+\/\d+\//) &&
       !document.location.href.match(/www.flickr.com\/groups\/[^\/]+\/discuss\/\d+\/#reply/)) { // exceptions should be the same as for checkForNewPostsInThread.subItemUrl
    	processThreadPage();
    }

} else if (document.location.href.match(/www.flickr.com\/help\/forum\/[^\/]+\/\d+/)) {
    processForumItemPage();

} else if (document.location.href.match(/www.flickr.com\/groups\/[^\/]+\/discuss/)) {
    if (!document.location.href.match(/www.flickr.com\/groups\/[^\/]+\/discuss\/\d+/) &&
        !document.location.href.match(/www.flickr.com\/groups\/[^\/]+\/discuss\/page\d+/)) { // exceptions same as for checkForNewPostsInGroup.subItemUrl
    	processDiscussPage();
    }

} else if (document.location.href.match(/www.flickr.com\/help\/forum\/[^\/]+/)) {
    if (!document.location.href.match(/www.flickr.com\/help\/forum\/[^\/]+\/\?page=\d+/)) {
    	processHelpForumPage();
    }

} else if (document.location.href.match(/www.flickr.com\/photos\/[^\/]+\/\d+/)) {
    if (!document.location.href.match(/www.flickr.com\/photos\/[^\/]+\/\d+\/stats/) &&
        !document.location.href.match(/www.flickr.com\/photos\/[^\/]+\/\d+\/groups/) &&
        !document.location.href.match(/www.flickr.com\/photos\/[^\/]+\/\d+\/galleries/) &&
        !document.location.href.match(/www.flickr.com\/photos\/[^\/]+\/\d+\/favorites/)) { // exceptions same as for checkForNewCommentsInPhotoPage.subItemUrl
        processPhotoPage();
    }

// skip ../photos/friends
} else if (document.location.href.match(/www.flickr.com\/photos\/(friends|upload)/)) {
    // just skip

// skip ../photos/username/stats
} else if (document.location.href.match(/www.flickr.com\/photos\/[^\/]+\/(?:page\d+)?$/)) {
    if (!document.location.href.match(/www.flickr.com\/photos\/[^\/]+\/\d+/)) { // exceptions same as for checkForNewPhotosInPhotostream
    	processPhotostreamPage();
    }

} else if (document.location.href.match(/www.flickr.com\/groups_members.gne.*(?!tab=)/) ||
           document.location.href.match(/www.flickr.com\/groups\/[^\/]+\/members\/$/)) {
    processGroupMembersPage();

} else if (document.location.href.match(/www.flickr.com\/groups\/[^\/]+\/pool\/(?!page)/)) { // exceptions same as for checkForNewPhotosInGroup
    if (document.location.href.match(/www.flickr.com\/groups\/[^\/]+\/pool\/\?donepending=1/)) {
    	processPendingItemsPage();
    } else {
    	if (!document.location.href.match(/\/pool\/tags\//) &&
    	    !document.location.href.match(/donepending=1/)) {
        	processGroupPoolPage();
    	}
    }

} else if (document.location.href.match(/www.flickr.com\/people\/[^\/]+\/contacts\/rev/)) {
    processReversedContactsPage();

} else if (document.location.href.match(/www.flickr.com\/groups\/[^\/]+\/admin\/pending\/(?!page)/)) {
    processPendingItemsPage();

} else if (document.location.href.match(/www.flickr.com\/services\/apps\/\d+/)) {
    if (!document.location.href.match(/www.flickr.com\/services\/apps\/\d+\/key/)) {
    	processAppPage();
    }

} else if (document.location.href.match(/www.flickr.com\/photos\/[^\/]+\/sets\/\d+\/comments/)) {
    processSetPage();

} else if (document.location.href.match(/www.flickr.com\/testimonials_manage.gne/)) {
    processPendingTestimonialsPage();
}
    

